Stabile 1.1 Version
This commit is contained in:
commit
1b8bf9e8df
50 changed files with 10710 additions and 0 deletions
621
COPYING
Executable file
621
COPYING
Executable file
|
|
@ -0,0 +1,621 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
47
ChangeLog.md
Executable file
47
ChangeLog.md
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
# CHANGELOG MODULE KUNDENKARTE FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||
|
||||
## 1.1 (2026-01)
|
||||
|
||||
### Neue Features
|
||||
- **Kontakt/Adressen-Unterstuetzung**: Favoriten und Anlagen koennen nun auch auf Kontakt-/Adressebene verwaltet werden
|
||||
- Ideal fuer Kunden mit mehreren Gebaeuden/Standorten
|
||||
- Neue Tabs "Favoriten" und "Anlagen" auf Kontaktkarten
|
||||
- Vollstaendige Trennung der Daten zwischen Kunde und Kontakten
|
||||
|
||||
### Verbesserungen
|
||||
- Mengen-Eingabe bei Favoriten vereinfacht (Textfeld + Speichern-Button)
|
||||
- Modul-Icon geaendert zu fa-id-card
|
||||
- Dokumentation aktualisiert
|
||||
|
||||
### Datenbank-Aenderungen
|
||||
- Neue Spalte `fk_contact` in Tabelle `llx_kundenkarte_favorite_products`
|
||||
- Neue Spalte `fk_contact` in Tabelle `llx_kundenkarte_anlage`
|
||||
- Neue Spalte `fk_contact` in Tabelle `llx_kundenkarte_societe_system`
|
||||
|
||||
### Hinweis zum Upgrade
|
||||
Nach dem Update bitte das Modul einmal deaktivieren und wieder aktivieren, damit die SQL-Aenderungen ausgefuehrt werden.
|
||||
|
||||
---
|
||||
|
||||
## 1.0
|
||||
|
||||
### Features
|
||||
- Favoriten-Produkte fuer Kunden
|
||||
- Produkte als Favoriten markieren
|
||||
- Standardmengen festlegen
|
||||
- Bestellungen aus Favoriten generieren
|
||||
- Sortierbare Liste
|
||||
|
||||
- Technische Anlagen (Baumstruktur)
|
||||
- Systemkategorien (Strom, Internet, Kabel, Sat)
|
||||
- Konfigurierbare Element-Typen
|
||||
- Individuelle Felder pro Typ
|
||||
- Datei-Upload mit Vorschau
|
||||
- Hierarchische Struktur
|
||||
|
||||
- Admin-Bereich
|
||||
- Systeme verwalten
|
||||
- Typen verwalten
|
||||
- Felder konfigurieren
|
||||
|
||||
Initial version
|
||||
86
README.md
Executable file
86
README.md
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
# KUNDENKARTE FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org)
|
||||
|
||||
## Features
|
||||
|
||||
Das KundenKarte-Modul erweitert Dolibarr um zwei wichtige Funktionen fuer Kunden und deren Kontakte/Adressen:
|
||||
|
||||
### Favoriten-Produkte
|
||||
- Verwalten von Lieblingsprodukten pro Kunde oder Kontakt/Adresse
|
||||
- Schnelle Bestellgenerierung aus Favoriten
|
||||
- Individuelle Standardmengen pro Produkt
|
||||
- Sortierbare Liste mit Drag & Drop oder Pfeiltasten
|
||||
|
||||
### Technische Anlagen (Anlagen)
|
||||
- Hierarchische Baumstruktur fuer technische Installationen
|
||||
- Flexible Systemkategorien (z.B. Strom, Internet, Kabel, Sat)
|
||||
- Konfigurierbare Element-Typen mit individuellen Feldern
|
||||
- Datei-Upload mit Bild-Vorschau und PDF-Anzeige
|
||||
- Separate Verwaltung pro Kunde oder pro Kontakt/Adresse (z.B. verschiedene Gebaeude)
|
||||
|
||||
### Kontakt/Adressen-Unterstuetzung
|
||||
- Beide Funktionen (Favoriten + Anlagen) sind sowohl auf Kundenebene als auch auf Kontakt-/Adressebene verfuegbar
|
||||
- Ideal fuer Kunden mit mehreren Standorten/Gebaeuden
|
||||
- Vollstaendige Trennung der Daten zwischen Kunde und Kontakten
|
||||
|
||||
## Tabs
|
||||
|
||||
Das Modul fuegt folgende Tabs hinzu:
|
||||
|
||||
| Tab | Objekt | Beschreibung |
|
||||
|-----|--------|--------------|
|
||||
| Favoriten | Kunde (Thirdparty) | Favoriten-Produkte fuer den Kunden |
|
||||
| Favoriten | Kontakt/Adresse | Favoriten-Produkte fuer einen spezifischen Kontakt |
|
||||
| Anlagen | Kunde (Thirdparty) | Technische Anlagen des Kunden |
|
||||
| Anlagen | Kontakt/Adresse | Technische Anlagen eines spezifischen Kontakts/Gebaeudes |
|
||||
|
||||
## Admin-Bereich
|
||||
|
||||
Im Admin-Bereich (Home > Setup > Module > KundenKarte) koennen Sie:
|
||||
|
||||
- **Anlagen-Systeme**: System-Kategorien anlegen (z.B. Strom, Internet)
|
||||
- **Element-Typen**: Geraetetypen definieren (z.B. Zaehler, Router, Wallbox)
|
||||
- **Typ-Felder**: Individuelle Felder pro Geraetetyp konfigurieren
|
||||
|
||||
## Berechtigungen
|
||||
|
||||
| Berechtigung | Beschreibung |
|
||||
|--------------|--------------|
|
||||
| kundenkarte read | Favoriten und Anlagen ansehen |
|
||||
| kundenkarte write | Favoriten und Anlagen bearbeiten |
|
||||
| kundenkarte delete | Favoriten und Anlagen loeschen |
|
||||
|
||||
## Installation
|
||||
|
||||
### Voraussetzungen
|
||||
- Dolibarr ERP & CRM >= 19.0
|
||||
- PHP >= 7.1
|
||||
|
||||
### Installation via ZIP
|
||||
1. ZIP-Datei herunterladen
|
||||
2. In Dolibarr: Home > Setup > Module > Externes Modul deployen
|
||||
3. ZIP-Datei hochladen
|
||||
4. Modul aktivieren unter Home > Setup > Module
|
||||
|
||||
### Manuelle Installation
|
||||
1. Modul-Ordner in `/custom/kundenkarte` kopieren
|
||||
2. In Dolibarr: Home > Setup > Module
|
||||
3. Modul "KundenKarte" aktivieren
|
||||
|
||||
### Nach der Aktivierung
|
||||
- Die SQL-Tabellen werden automatisch erstellt
|
||||
- Systemkategorien und Typen im Admin-Bereich anlegen
|
||||
- Fertig!
|
||||
|
||||
## Translations
|
||||
|
||||
Uebersetzungen befinden sich in:
|
||||
- `langs/de_DE/kundenkarte.lang` (Deutsch)
|
||||
- `langs/en_US/kundenkarte.lang` (Englisch)
|
||||
|
||||
## License
|
||||
|
||||
GPLv3 or (at your option) any later version. See file COPYING for more information.
|
||||
|
||||
## Author
|
||||
|
||||
Alles Watt laeuft - Eduard Wisch
|
||||
118
admin/about.php
Executable file
118
admin/about.php
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
/* Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||
* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file kundenkarte/admin/about.php
|
||||
* \ingroup kundenkarte
|
||||
* \brief About page of module KundenKarte.
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
|
||||
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
|
||||
}
|
||||
// Try main.inc.php using relative path
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../../main.inc.php")) {
|
||||
$res = @include "../../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
// Libraries
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
|
||||
require_once '../lib/kundenkarte.lib.php';
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var HookManager $hookmanager
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
// Translations
|
||||
$langs->loadLangs(array("errors", "admin", "kundenkarte@kundenkarte"));
|
||||
|
||||
// Access control
|
||||
if (!$user->admin) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Parameters
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$backtopage = GETPOST('backtopage', 'alpha');
|
||||
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
// None
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$form = new Form($db);
|
||||
|
||||
$help_url = '';
|
||||
$title = "KundenKarteSetup";
|
||||
|
||||
llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-kundenkarte page-admin_about');
|
||||
|
||||
// Subheader
|
||||
$linkback = '<a href="'.($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1').'">'.$langs->trans("BackToModuleList").'</a>';
|
||||
|
||||
print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
|
||||
|
||||
// Configuration header
|
||||
$head = kundenkarteAdminPrepareHead();
|
||||
print dol_get_fiche_head($head, 'about', $langs->trans($title), 0, 'kundenkarte@kundenkarte');
|
||||
|
||||
dol_include_once('/kundenkarte/core/modules/modKundenKarte.class.php');
|
||||
$tmpmodule = new modKundenKarte($db);
|
||||
print $tmpmodule->getDescLong();
|
||||
|
||||
// Page end
|
||||
print dol_get_fiche_end();
|
||||
llxFooter();
|
||||
$db->close();
|
||||
254
admin/anlage_systems.php
Executable file
254
admin/anlage_systems.php
Executable file
|
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* Admin page to manage installation system categories
|
||||
*/
|
||||
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
||||
|
||||
$langs->loadLangs(array('admin', 'kundenkarte@kundenkarte'));
|
||||
|
||||
// Security check
|
||||
if (!$user->admin && !$user->hasRight('kundenkarte', 'admin')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
$systemId = GETPOSTINT('systemid');
|
||||
|
||||
$form = new Form($db);
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
if ($action == 'add') {
|
||||
$code = GETPOST('code', 'aZ09');
|
||||
$label = GETPOST('label', 'alphanohtml');
|
||||
$picto = GETPOST('picto', 'alphanohtml');
|
||||
$color = GETPOST('color', 'alphanohtml');
|
||||
$position = GETPOSTINT('position');
|
||||
|
||||
if (empty($code) || empty($label)) {
|
||||
setEventMessages($langs->trans('ErrorFieldRequired'), null, 'errors');
|
||||
} else {
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system";
|
||||
$sql .= " (code, label, picto, color, position, active, entity)";
|
||||
$sql .= " VALUES ('".$db->escape(strtoupper($code))."', '".$db->escape($label)."',";
|
||||
$sql .= " ".($picto ? "'".$db->escape($picto)."'" : "NULL").",";
|
||||
$sql .= " ".($color ? "'".$db->escape($color)."'" : "NULL").",";
|
||||
$sql .= " ".((int) $position).", 1, 0)";
|
||||
|
||||
$result = $db->query($sql);
|
||||
if ($result) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($db->lasterror(), null, 'errors');
|
||||
}
|
||||
}
|
||||
$action = '';
|
||||
}
|
||||
|
||||
if ($action == 'update') {
|
||||
$code = GETPOST('code', 'aZ09');
|
||||
$label = GETPOST('label', 'alphanohtml');
|
||||
$picto = GETPOST('picto', 'alphanohtml');
|
||||
$color = GETPOST('color', 'alphanohtml');
|
||||
$position = GETPOSTINT('position');
|
||||
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system SET";
|
||||
$sql .= " code = '".$db->escape(strtoupper($code))."'";
|
||||
$sql .= ", label = '".$db->escape($label)."'";
|
||||
$sql .= ", picto = ".($picto ? "'".$db->escape($picto)."'" : "NULL");
|
||||
$sql .= ", color = ".($color ? "'".$db->escape($color)."'" : "NULL");
|
||||
$sql .= ", position = ".((int) $position);
|
||||
$sql .= " WHERE rowid = ".((int) $systemId);
|
||||
|
||||
$result = $db->query($sql);
|
||||
if ($result) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($db->lasterror(), null, 'errors');
|
||||
}
|
||||
$action = '';
|
||||
}
|
||||
|
||||
if ($action == 'confirm_delete' && $confirm == 'yes') {
|
||||
// Check if in use
|
||||
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."kundenkarte_anlage WHERE fk_system = ".((int) $systemId);
|
||||
$resql = $db->query($sql);
|
||||
$obj = $db->fetch_object($resql);
|
||||
|
||||
if ($obj->cnt > 0) {
|
||||
setEventMessages($langs->trans('ErrorSystemInUse'), null, 'errors');
|
||||
} else {
|
||||
// Also delete types for this system
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type WHERE fk_system = ".((int) $systemId);
|
||||
$db->query($sql);
|
||||
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system WHERE rowid = ".((int) $systemId);
|
||||
$result = $db->query($sql);
|
||||
if ($result) {
|
||||
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
|
||||
}
|
||||
}
|
||||
$action = '';
|
||||
}
|
||||
|
||||
if ($action == 'activate') {
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system SET active = 1 WHERE rowid = ".((int) $systemId);
|
||||
$db->query($sql);
|
||||
$action = '';
|
||||
}
|
||||
|
||||
if ($action == 'deactivate') {
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system SET active = 0 WHERE rowid = ".((int) $systemId);
|
||||
$db->query($sql);
|
||||
$action = '';
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$title = $langs->trans('AnlagenSystems');
|
||||
|
||||
// Include CSS and JS
|
||||
$morejs = array('/kundenkarte/js/kundenkarte.js');
|
||||
$morecss = array('/kundenkarte/css/kundenkarte.css');
|
||||
|
||||
llxHeader('', $title, '', '', 0, 0, $morejs, $morecss);
|
||||
|
||||
$head = kundenkarteAdminPrepareHead();
|
||||
print dol_get_fiche_head($head, 'systems', $langs->trans('ModuleKundenKarteName'), -1, 'fa-file');
|
||||
|
||||
// Confirmation
|
||||
if ($action == 'delete') {
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?systemid='.$systemId,
|
||||
$langs->trans('Delete'),
|
||||
$langs->trans('ConfirmDeleteSystem'),
|
||||
'confirm_delete',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
// Add form
|
||||
if ($action == 'create' || $action == 'edit') {
|
||||
$system = null;
|
||||
if ($action == 'edit' && $systemId > 0) {
|
||||
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system WHERE rowid = ".((int) $systemId);
|
||||
$resql = $db->query($sql);
|
||||
$system = $db->fetch_object($resql);
|
||||
}
|
||||
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="'.($action == 'edit' ? 'update' : 'add').'">';
|
||||
if ($action == 'edit') {
|
||||
print '<input type="hidden" name="systemid" value="'.$systemId.'">';
|
||||
}
|
||||
|
||||
print '<table class="border centpercent">';
|
||||
|
||||
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('SystemCode').'</td>';
|
||||
print '<td><input type="text" name="code" class="flat minwidth200" value="'.dol_escape_htmltag($system ? $system->code : '').'" maxlength="32" required></td></tr>';
|
||||
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans('SystemLabel').'</td>';
|
||||
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($system ? $system->label : '').'" required></td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans('SystemPicto').'</td>';
|
||||
print '<td><div class="kundenkarte-icon-picker-wrapper">';
|
||||
print '<span class="kundenkarte-icon-preview">';
|
||||
if ($system && $system->picto) {
|
||||
print kundenkarte_render_icon($system->picto);
|
||||
}
|
||||
print '</span>';
|
||||
print '<input type="text" name="picto" class="flat minwidth200" value="'.dol_escape_htmltag($system ? $system->picto : '').'" placeholder="fa-bolt">';
|
||||
print '<button type="button" class="kundenkarte-icon-picker-btn" data-input="picto"><i class="fa fa-th"></i> '.$langs->trans('SelectIcon').'</button>';
|
||||
print '</div></td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans('SystemColor').'</td>';
|
||||
print '<td><input type="color" name="color" value="'.dol_escape_htmltag($system ? $system->color : '#3498db').'"></td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans('Position').'</td>';
|
||||
print '<td><input type="number" name="position" class="flat" value="'.($system ? $system->position : 0).'" min="0"></td></tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
print '<div class="center" style="margin-top:20px;">';
|
||||
print '<button type="submit" class="button">'.$langs->trans('Save').'</button>';
|
||||
print ' <a class="button button-cancel" href="'.$_SERVER['PHP_SELF'].'">'.$langs->trans('Cancel').'</a>';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
|
||||
} else {
|
||||
// List
|
||||
print '<div style="margin-bottom:15px;">';
|
||||
print '<a class="button" href="'.$_SERVER['PHP_SELF'].'?action=create">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
|
||||
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system ORDER BY position ASC, label ASC";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans('SystemCode').'</th>';
|
||||
print '<th>'.$langs->trans('SystemLabel').'</th>';
|
||||
print '<th>'.$langs->trans('SystemPicto').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Position').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Status').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Actions').'</th>';
|
||||
print '</tr>';
|
||||
|
||||
if ($resql) {
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
print '<tr class="oddeven">';
|
||||
|
||||
print '<td>'.dol_escape_htmltag($obj->code).'</td>';
|
||||
print '<td>'.dol_escape_htmltag($obj->label).'</td>';
|
||||
print '<td>';
|
||||
if ($obj->picto) {
|
||||
print kundenkarte_render_icon($obj->picto, '', 'color:'.$obj->color.';').' ';
|
||||
print dol_escape_htmltag($obj->picto);
|
||||
}
|
||||
print '</td>';
|
||||
print '<td class="center">'.$obj->position.'</td>';
|
||||
|
||||
print '<td class="center">';
|
||||
if ($obj->active) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=deactivate&systemid='.$obj->rowid.'&token='.newToken().'">'.img_picto($langs->trans('Enabled'), 'switch_on').'</a>';
|
||||
} else {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=activate&systemid='.$obj->rowid.'&token='.newToken().'">'.img_picto($langs->trans('Disabled'), 'switch_off').'</a>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
print '<td class="center nowraponall">';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=edit&systemid='.$obj->rowid.'">'.img_edit().'</a>';
|
||||
print ' <a href="'.$_SERVER['PHP_SELF'].'?action=delete&systemid='.$obj->rowid.'" class="deletelink">'.img_delete().'</a>';
|
||||
print '</td>';
|
||||
|
||||
print '</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
}
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
647
admin/anlage_types.php
Executable file
647
admin/anlage_types.php
Executable file
|
|
@ -0,0 +1,647 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* Admin page to manage installation element types
|
||||
*/
|
||||
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
||||
dol_include_once('/kundenkarte/class/anlagetype.class.php');
|
||||
|
||||
$langs->loadLangs(array('admin', 'kundenkarte@kundenkarte'));
|
||||
|
||||
// Security check
|
||||
if (!$user->admin && !$user->hasRight('kundenkarte', 'admin')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
$typeId = GETPOSTINT('typeid');
|
||||
$systemFilter = GETPOSTINT('system');
|
||||
|
||||
$form = new Form($db);
|
||||
$anlageType = new AnlageType($db);
|
||||
|
||||
// Load systems
|
||||
$systems = array();
|
||||
$sql = "SELECT rowid, code, label FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system WHERE active = 1 ORDER BY position ASC";
|
||||
$resql = $db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$systems[$obj->rowid] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
if ($action == 'add') {
|
||||
$anlageType->ref = GETPOST('ref', 'aZ09');
|
||||
$anlageType->label = GETPOST('label', 'alphanohtml');
|
||||
$anlageType->label_short = GETPOST('label_short', 'alphanohtml');
|
||||
$anlageType->description = GETPOST('description', 'restricthtml');
|
||||
$anlageType->fk_system = GETPOSTINT('fk_system');
|
||||
$anlageType->can_have_children = GETPOSTINT('can_have_children');
|
||||
$anlageType->can_be_nested = GETPOSTINT('can_be_nested');
|
||||
$anlageType->allowed_parent_types = GETPOST('allowed_parent_types', 'alphanohtml');
|
||||
$anlageType->picto = GETPOST('picto', 'alphanohtml');
|
||||
$anlageType->color = GETPOST('color', 'alphanohtml');
|
||||
$anlageType->position = GETPOSTINT('position');
|
||||
$anlageType->active = 1;
|
||||
|
||||
if (empty($anlageType->ref) || empty($anlageType->label) || empty($anlageType->fk_system)) {
|
||||
setEventMessages($langs->trans('ErrorFieldRequired'), null, 'errors');
|
||||
$action = 'create';
|
||||
} else {
|
||||
$result = $anlageType->create($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?system='.$anlageType->fk_system);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($anlageType->error, $anlageType->errors, 'errors');
|
||||
$action = 'create';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($action == 'update') {
|
||||
$anlageType->fetch($typeId);
|
||||
$anlageType->ref = GETPOST('ref', 'aZ09');
|
||||
$anlageType->label = GETPOST('label', 'alphanohtml');
|
||||
$anlageType->label_short = GETPOST('label_short', 'alphanohtml');
|
||||
$anlageType->description = GETPOST('description', 'restricthtml');
|
||||
$anlageType->fk_system = GETPOSTINT('fk_system');
|
||||
$anlageType->can_have_children = GETPOSTINT('can_have_children');
|
||||
$anlageType->can_be_nested = GETPOSTINT('can_be_nested');
|
||||
$anlageType->allowed_parent_types = GETPOST('allowed_parent_types', 'alphanohtml');
|
||||
$anlageType->picto = GETPOST('picto', 'alphanohtml');
|
||||
$anlageType->color = GETPOST('color', 'alphanohtml');
|
||||
$anlageType->position = GETPOSTINT('position');
|
||||
|
||||
$result = $anlageType->update($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?system='.$anlageType->fk_system);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($anlageType->error, $anlageType->errors, 'errors');
|
||||
$action = 'edit';
|
||||
}
|
||||
}
|
||||
|
||||
if ($action == 'confirm_delete' && $confirm == 'yes') {
|
||||
$anlageType->fetch($typeId);
|
||||
$result = $anlageType->delete($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($anlageType->error, $anlageType->errors, 'errors');
|
||||
}
|
||||
$action = '';
|
||||
}
|
||||
|
||||
if ($action == 'activate') {
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage_type SET active = 1 WHERE rowid = ".((int) $typeId);
|
||||
$db->query($sql);
|
||||
$action = '';
|
||||
}
|
||||
|
||||
if ($action == 'deactivate') {
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage_type SET active = 0 WHERE rowid = ".((int) $typeId);
|
||||
$db->query($sql);
|
||||
$action = '';
|
||||
}
|
||||
|
||||
// Field actions
|
||||
$fieldId = GETPOSTINT('fieldid');
|
||||
|
||||
if ($action == 'add_field') {
|
||||
$fieldCode = GETPOST('field_code', 'aZ09');
|
||||
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
||||
$fieldType = GETPOST('field_type', 'aZ09');
|
||||
$fieldOptions = GETPOST('field_options', 'restricthtml');
|
||||
$showInTree = GETPOSTINT('show_in_tree');
|
||||
$showInHover = GETPOSTINT('show_in_hover');
|
||||
$isRequired = GETPOSTINT('is_required');
|
||||
$fieldPosition = GETPOSTINT('field_position');
|
||||
|
||||
if (empty($fieldCode) || empty($fieldLabel) || empty($fieldType)) {
|
||||
setEventMessages($langs->trans('ErrorFieldRequired'), null, 'errors');
|
||||
} else {
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field";
|
||||
$sql .= " (fk_anlage_type, field_code, field_label, field_type, field_options, show_in_tree, show_in_hover, required, position, active)";
|
||||
$sql .= " VALUES (".((int) $typeId).", '".$db->escape($fieldCode)."', '".$db->escape($fieldLabel)."',";
|
||||
$sql .= " '".$db->escape($fieldType)."', '".$db->escape($fieldOptions)."',";
|
||||
$sql .= " ".((int) $showInTree).", ".((int) $showInHover).", ".((int) $isRequired).", ".((int) $fieldPosition).", 1)";
|
||||
|
||||
if ($db->query($sql)) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($db->lasterror(), null, 'errors');
|
||||
}
|
||||
}
|
||||
$action = 'edit';
|
||||
}
|
||||
|
||||
if ($action == 'update_field') {
|
||||
$fieldCode = GETPOST('field_code', 'aZ09');
|
||||
$fieldLabel = GETPOST('field_label', 'alphanohtml');
|
||||
$fieldType = GETPOST('field_type', 'aZ09');
|
||||
$fieldOptions = GETPOST('field_options', 'restricthtml');
|
||||
$showInTree = GETPOSTINT('show_in_tree');
|
||||
$showInHover = GETPOSTINT('show_in_hover');
|
||||
$isRequired = GETPOSTINT('is_required');
|
||||
$fieldPosition = GETPOSTINT('field_position');
|
||||
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field SET";
|
||||
$sql .= " field_code = '".$db->escape($fieldCode)."',";
|
||||
$sql .= " field_label = '".$db->escape($fieldLabel)."',";
|
||||
$sql .= " field_type = '".$db->escape($fieldType)."',";
|
||||
$sql .= " field_options = '".$db->escape($fieldOptions)."',";
|
||||
$sql .= " show_in_tree = ".((int) $showInTree).",";
|
||||
$sql .= " show_in_hover = ".((int) $showInHover).",";
|
||||
$sql .= " required = ".((int) $isRequired).",";
|
||||
$sql .= " position = ".((int) $fieldPosition);
|
||||
$sql .= " WHERE rowid = ".((int) $fieldId);
|
||||
|
||||
if ($db->query($sql)) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($db->lasterror(), null, 'errors');
|
||||
}
|
||||
$action = 'edit';
|
||||
}
|
||||
|
||||
if ($action == 'confirm_delete_field' && $confirm == 'yes') {
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field WHERE rowid = ".((int) $fieldId);
|
||||
if ($db->query($sql)) {
|
||||
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($db->lasterror(), null, 'errors');
|
||||
}
|
||||
$action = 'edit';
|
||||
}
|
||||
|
||||
if ($action == 'activate_field') {
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field SET active = 1 WHERE rowid = ".((int) $fieldId);
|
||||
$db->query($sql);
|
||||
$action = 'edit';
|
||||
}
|
||||
|
||||
if ($action == 'deactivate_field') {
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field SET active = 0 WHERE rowid = ".((int) $fieldId);
|
||||
$db->query($sql);
|
||||
$action = 'edit';
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$title = $langs->trans('AnlagenTypes');
|
||||
|
||||
// Include CSS and JS
|
||||
$morejs = array('/kundenkarte/js/kundenkarte.js');
|
||||
$morecss = array('/kundenkarte/css/kundenkarte.css');
|
||||
|
||||
llxHeader('', $title, '', '', 0, 0, $morejs, $morecss);
|
||||
|
||||
$head = kundenkarteAdminPrepareHead();
|
||||
print dol_get_fiche_head($head, 'types', $langs->trans('ModuleKundenKarteName'), -1, 'fa-file');
|
||||
|
||||
// Confirmation
|
||||
if ($action == 'delete') {
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?typeid='.$typeId.'&system='.$systemFilter,
|
||||
$langs->trans('Delete'),
|
||||
$langs->trans('ConfirmDeleteType'),
|
||||
'confirm_delete',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
// Add/Edit form
|
||||
if (in_array($action, array('create', 'edit'))) {
|
||||
if ($action == 'edit' && $typeId > 0) {
|
||||
$anlageType->fetch($typeId);
|
||||
}
|
||||
|
||||
// Get all types for parent selection
|
||||
$allTypes = $anlageType->fetchAllBySystem(0, 0);
|
||||
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="'.($action == 'edit' ? 'update' : 'add').'">';
|
||||
if ($action == 'edit') {
|
||||
print '<input type="hidden" name="typeid" value="'.$typeId.'">';
|
||||
}
|
||||
|
||||
print '<table class="border centpercent">';
|
||||
|
||||
// System
|
||||
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('System').'</td>';
|
||||
print '<td><select name="fk_system" class="flat minwidth200" required>';
|
||||
print '<option value="">'.$langs->trans('SelectSystem').'</option>';
|
||||
foreach ($systems as $sys) {
|
||||
$sel = ($anlageType->fk_system == $sys->rowid) ? ' selected' : '';
|
||||
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
|
||||
}
|
||||
print '</select></td></tr>';
|
||||
|
||||
// Reference
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans('TypeRef').'</td>';
|
||||
print '<td><input type="text" name="ref" class="flat minwidth200" value="'.dol_escape_htmltag($anlageType->ref).'" maxlength="64" required>';
|
||||
print ' <span class="opacitymedium">(UPPERCASE, no spaces)</span></td></tr>';
|
||||
|
||||
// Label
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans('TypeLabel').'</td>';
|
||||
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($anlageType->label).'" required></td></tr>';
|
||||
|
||||
// Short label
|
||||
print '<tr><td>'.$langs->trans('TypeShortLabel').'</td>';
|
||||
print '<td><input type="text" name="label_short" class="flat" value="'.dol_escape_htmltag($anlageType->label_short).'" maxlength="64"></td></tr>';
|
||||
|
||||
// Description
|
||||
print '<tr><td>'.$langs->trans('Description').'</td>';
|
||||
print '<td><textarea name="description" class="flat minwidth300" rows="2">'.dol_escape_htmltag($anlageType->description).'</textarea></td></tr>';
|
||||
|
||||
// Can have children
|
||||
print '<tr><td>'.$langs->trans('CanHaveChildren').'</td>';
|
||||
print '<td><input type="checkbox" name="can_have_children" value="1"'.($anlageType->can_have_children ? ' checked' : '').'></td></tr>';
|
||||
|
||||
// Can be nested
|
||||
print '<tr><td>'.$langs->trans('CanBeNested').'</td>';
|
||||
print '<td><input type="checkbox" name="can_be_nested" value="1"'.($anlageType->can_be_nested ? ' checked' : '').'>';
|
||||
print ' <span class="opacitymedium">('.$langs->trans('SameTypeUnderItself').')</span></td></tr>';
|
||||
|
||||
// Allowed parent types - with multi-select UI
|
||||
print '<tr><td>'.$langs->trans('AllowedParentTypes').'</td>';
|
||||
print '<td>';
|
||||
|
||||
// Hidden field to store the actual value
|
||||
print '<input type="hidden" name="allowed_parent_types" id="allowed_parent_types" value="'.dol_escape_htmltag($anlageType->allowed_parent_types).'">';
|
||||
|
||||
// Selection UI
|
||||
print '<div class="kundenkarte-parent-types-selector">';
|
||||
|
||||
// Select dropdown with add button
|
||||
print '<div style="display:flex;gap:5px;margin-bottom:10px;align-items:center;">';
|
||||
print '<select id="parent_type_select" class="flat" style="height:30px;">';
|
||||
print '<option value="">'.$langs->trans('SelectType').'</option>';
|
||||
foreach ($allTypes as $t) {
|
||||
// Don't show current type in list (can't be parent of itself unless can_be_nested)
|
||||
if ($action == 'edit' && $t->id == $typeId) continue;
|
||||
print '<option value="'.dol_escape_htmltag($t->ref).'" data-label="'.dol_escape_htmltag($t->label).'">'.dol_escape_htmltag($t->ref).' - '.dol_escape_htmltag($t->label).'</option>';
|
||||
}
|
||||
print '</select>';
|
||||
print '<button type="button" class="button" id="add_parent_type_btn"><i class="fa fa-plus"></i> '.$langs->trans('Add').'</button>';
|
||||
print '</div>';
|
||||
|
||||
// List of selected parent types
|
||||
print '<div id="selected_parent_types" class="kundenkarte-selected-items">';
|
||||
// Will be filled by JavaScript
|
||||
print '</div>';
|
||||
|
||||
print '</div>';
|
||||
|
||||
print '<span class="opacitymedium">('.$langs->trans('AllowedParentTypesHelp').')</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
// Icon
|
||||
print '<tr><td>'.$langs->trans('SystemPicto').'</td>';
|
||||
print '<td><div class="kundenkarte-icon-picker-wrapper">';
|
||||
print '<span class="kundenkarte-icon-preview">';
|
||||
if ($anlageType->picto) {
|
||||
print kundenkarte_render_icon($anlageType->picto);
|
||||
}
|
||||
print '</span>';
|
||||
print '<input type="text" name="picto" class="flat minwidth200" value="'.dol_escape_htmltag($anlageType->picto).'" placeholder="fa-cube">';
|
||||
print '<button type="button" class="kundenkarte-icon-picker-btn" data-input="picto"><i class="fa fa-th"></i> '.$langs->trans('SelectIcon').'</button>';
|
||||
print '</div></td></tr>';
|
||||
|
||||
// Position
|
||||
print '<tr><td>'.$langs->trans('Position').'</td>';
|
||||
print '<td><input type="number" name="position" class="flat" value="'.($anlageType->position ?: 0).'" min="0"></td></tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
print '<div class="center" style="margin-top:20px;">';
|
||||
print '<button type="submit" class="button">'.$langs->trans('Save').'</button>';
|
||||
print ' <a class="button button-cancel" href="'.$_SERVER['PHP_SELF'].'?system='.$systemFilter.'">'.$langs->trans('Cancel').'</a>';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
|
||||
// Fields management for existing type
|
||||
if ($action == 'edit' && $typeId > 0) {
|
||||
$editFieldId = GETPOSTINT('editfield');
|
||||
|
||||
// Confirmation for field deletion
|
||||
if ($action == 'delete_field') {
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?action=edit&typeid='.$typeId.'&fieldid='.$fieldId.'&system='.$systemFilter,
|
||||
$langs->trans('Delete'),
|
||||
$langs->trans('ConfirmDeleteField'),
|
||||
'confirm_delete_field',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
print '<br><br>';
|
||||
print '<h3>'.$langs->trans('AnlagenTypeFields').'</h3>';
|
||||
|
||||
$fields = $anlageType->fetchFields(0);
|
||||
|
||||
// Field types available
|
||||
$fieldTypes = array(
|
||||
'text' => 'Textfeld (einzeilig)',
|
||||
'textarea' => 'Textfeld (mehrzeilig)',
|
||||
'number' => 'Zahlenfeld',
|
||||
'select' => 'Dropdown-Auswahl',
|
||||
'date' => 'Datumsfeld',
|
||||
'checkbox' => 'Checkbox (Ja/Nein)',
|
||||
);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans('FieldCode').'</th>';
|
||||
print '<th>'.$langs->trans('FieldLabel').'</th>';
|
||||
print '<th>'.$langs->trans('FieldType').'</th>';
|
||||
print '<th>'.$langs->trans('FieldOptions').'</th>';
|
||||
print '<th class="center">'.$langs->trans('ShowInTree').'</th>';
|
||||
print '<th class="center">'.$langs->trans('ShowInHover').'</th>';
|
||||
print '<th class="center">'.$langs->trans('IsRequired').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Position').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Status').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Actions').'</th>';
|
||||
print '</tr>';
|
||||
|
||||
foreach ($fields as $field) {
|
||||
// Check if we're editing this field
|
||||
if ($editFieldId == $field->id) {
|
||||
// Edit row
|
||||
print '<tr class="oddeven">';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="update_field">';
|
||||
print '<input type="hidden" name="typeid" value="'.$typeId.'">';
|
||||
print '<input type="hidden" name="fieldid" value="'.$field->id.'">';
|
||||
print '<input type="hidden" name="system" value="'.$systemFilter.'">';
|
||||
|
||||
print '<td><input type="text" name="field_code" class="flat minwidth100" value="'.dol_escape_htmltag($field->field_code).'" required></td>';
|
||||
print '<td><input type="text" name="field_label" class="flat minwidth150" value="'.dol_escape_htmltag($field->field_label).'" required></td>';
|
||||
print '<td><select name="field_type" class="flat">';
|
||||
foreach ($fieldTypes as $ftype => $flabel) {
|
||||
$sel = ($field->field_type == $ftype) ? ' selected' : '';
|
||||
print '<option value="'.$ftype.'"'.$sel.'>'.$flabel.'</option>';
|
||||
}
|
||||
print '</select></td>';
|
||||
print '<td><input type="text" name="field_options" class="flat minwidth100" value="'.dol_escape_htmltag($field->field_options).'" placeholder="opt1|opt2|opt3"></td>';
|
||||
print '<td class="center"><input type="checkbox" name="show_in_tree" value="1"'.($field->show_in_tree ? ' checked' : '').'></td>';
|
||||
print '<td class="center"><input type="checkbox" name="show_in_hover" value="1"'.($field->show_in_hover ? ' checked' : '').'></td>';
|
||||
print '<td class="center"><input type="checkbox" name="is_required" value="1"'.($field->required ? ' checked' : '').'></td>';
|
||||
print '<td class="center"><input type="number" name="field_position" class="flat" style="width:50px;" value="'.$field->position.'" min="0"></td>';
|
||||
print '<td></td>';
|
||||
print '<td class="center nowraponall">';
|
||||
print '<button type="submit" class="button buttongen marginrightonly" title="'.$langs->trans('Save').'"><i class="fa fa-save"></i></button>';
|
||||
print '<a class="button buttongen" href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$typeId.'&system='.$systemFilter.'" title="'.$langs->trans('Cancel').'"><i class="fa fa-times"></i></a>';
|
||||
print '</td>';
|
||||
print '</form>';
|
||||
print '</tr>';
|
||||
} else {
|
||||
// Display row
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.dol_escape_htmltag($field->field_code).'</td>';
|
||||
print '<td>'.dol_escape_htmltag($field->field_label).'</td>';
|
||||
print '<td>'.dol_escape_htmltag($fieldTypes[$field->field_type] ?? $field->field_type).'</td>';
|
||||
print '<td class="small opacitymedium">'.dol_escape_htmltag(dol_trunc($field->field_options, 20)).'</td>';
|
||||
print '<td class="center">'.($field->show_in_tree ? img_picto('', 'tick') : '').'</td>';
|
||||
print '<td class="center">'.($field->show_in_hover ? img_picto('', 'tick') : '').'</td>';
|
||||
print '<td class="center">'.($field->required ? img_picto('', 'tick') : '').'</td>';
|
||||
print '<td class="center">'.$field->position.'</td>';
|
||||
print '<td class="center">';
|
||||
if ($field->active) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=deactivate_field&typeid='.$typeId.'&fieldid='.$field->id.'&system='.$systemFilter.'&token='.newToken().'">'.img_picto($langs->trans('Enabled'), 'switch_on').'</a>';
|
||||
} else {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=activate_field&typeid='.$typeId.'&fieldid='.$field->id.'&system='.$systemFilter.'&token='.newToken().'">'.img_picto($langs->trans('Disabled'), 'switch_off').'</a>';
|
||||
}
|
||||
print '</td>';
|
||||
print '<td class="center nowraponall">';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$typeId.'&editfield='.$field->id.'&system='.$systemFilter.'">'.img_edit().'</a>';
|
||||
print ' <a href="'.$_SERVER['PHP_SELF'].'?action=delete_field&typeid='.$typeId.'&fieldid='.$field->id.'&system='.$systemFilter.'" class="deletelink">'.img_delete().'</a>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
print '<tr class="oddeven"><td colspan="10" class="opacitymedium">'.$langs->trans('NoFieldsDefined').'</td></tr>';
|
||||
}
|
||||
|
||||
// Add new field row
|
||||
print '<tr class="oddeven liste_titre_add">';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="add_field">';
|
||||
print '<input type="hidden" name="typeid" value="'.$typeId.'">';
|
||||
print '<input type="hidden" name="system" value="'.$systemFilter.'">';
|
||||
|
||||
print '<td><input type="text" name="field_code" class="flat minwidth100" placeholder="CODE" required></td>';
|
||||
print '<td><input type="text" name="field_label" class="flat minwidth150" placeholder="'.$langs->trans('FieldLabel').'" required></td>';
|
||||
print '<td><select name="field_type" class="flat" required>';
|
||||
print '<option value="">'.$langs->trans('Select').'</option>';
|
||||
foreach ($fieldTypes as $ftype => $flabel) {
|
||||
print '<option value="'.$ftype.'">'.$flabel.'</option>';
|
||||
}
|
||||
print '</select></td>';
|
||||
print '<td><input type="text" name="field_options" class="flat minwidth100" placeholder="opt1|opt2"></td>';
|
||||
print '<td class="center"><input type="checkbox" name="show_in_tree" value="1"></td>';
|
||||
print '<td class="center"><input type="checkbox" name="show_in_hover" value="1" checked></td>';
|
||||
print '<td class="center"><input type="checkbox" name="is_required" value="1"></td>';
|
||||
print '<td class="center"><input type="number" name="field_position" class="flat" style="width:50px;" value="0" min="0"></td>';
|
||||
print '<td></td>';
|
||||
print '<td class="center"><button type="submit" class="button buttongen"><i class="fa fa-plus"></i> '.$langs->trans('Add').'</button></td>';
|
||||
|
||||
print '</form>';
|
||||
print '</tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Help box for field options
|
||||
print '<div class="info" style="margin-top:15px;">';
|
||||
print '<p><strong><i class="fa fa-info-circle"></i> Hilfe: Feld-Optionen nach Feldtyp</strong></p>';
|
||||
print '<table class="noborder" style="margin-top:10px;">';
|
||||
print '<tr><td style="width:200px;"><strong>Textfeld (einzeilig)</strong></td><td>Keine Optionen nötig</td></tr>';
|
||||
print '<tr><td><strong>Textfeld (mehrzeilig)</strong></td><td>Keine Optionen nötig</td></tr>';
|
||||
print '<tr><td><strong>Zahlenfeld</strong></td><td>Optional: <code>min:0|max:100|step:0.1</code></td></tr>';
|
||||
print '<tr><td><strong>Dropdown-Auswahl</strong></td><td><span style="color:#c00;">Pflicht!</span> Optionen mit <code>|</code> trennen, z.B.: <code>Option A|Option B|Option C</code></td></tr>';
|
||||
print '<tr><td><strong>Datumsfeld</strong></td><td>Keine Optionen nötig</td></tr>';
|
||||
print '<tr><td><strong>Checkbox (Ja/Nein)</strong></td><td>Keine Optionen nötig</td></tr>';
|
||||
print '</table>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
} else {
|
||||
// System filter
|
||||
print '<form method="GET" action="'.$_SERVER['PHP_SELF'].'" style="margin-bottom:15px;">';
|
||||
print $langs->trans('FilterBySystem').': ';
|
||||
print '<select name="system" class="flat" onchange="this.form.submit();">';
|
||||
print '<option value="0">'.$langs->trans('All').'</option>';
|
||||
foreach ($systems as $sys) {
|
||||
$sel = ($systemFilter == $sys->rowid) ? ' selected' : '';
|
||||
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
|
||||
}
|
||||
print '</select>';
|
||||
print '</form>';
|
||||
|
||||
// Add button
|
||||
print '<div style="margin-bottom:15px;">';
|
||||
print '<a class="button" href="'.$_SERVER['PHP_SELF'].'?action=create&system='.$systemFilter.'">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddType');
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
|
||||
// List
|
||||
$types = $anlageType->fetchAllBySystem($systemFilter, 0);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans('TypeRef').'</th>';
|
||||
print '<th>'.$langs->trans('TypeLabel').'</th>';
|
||||
print '<th>'.$langs->trans('System').'</th>';
|
||||
print '<th class="center">'.$langs->trans('CanHaveChildren').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Position').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Status').'</th>';
|
||||
print '<th class="center">'.$langs->trans('Actions').'</th>';
|
||||
print '</tr>';
|
||||
|
||||
foreach ($types as $type) {
|
||||
print '<tr class="oddeven">';
|
||||
|
||||
print '<td>';
|
||||
if ($type->picto) {
|
||||
print kundenkarte_render_icon($type->picto).' ';
|
||||
}
|
||||
print dol_escape_htmltag($type->ref).'</td>';
|
||||
|
||||
print '<td>'.dol_escape_htmltag($type->label);
|
||||
if ($type->label_short) {
|
||||
print ' <span class="opacitymedium">('.$type->label_short.')</span>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
print '<td>'.dol_escape_htmltag($type->system_label).'</td>';
|
||||
print '<td class="center">'.($type->can_have_children ? img_picto('', 'tick') : '').'</td>';
|
||||
print '<td class="center">'.$type->position.'</td>';
|
||||
|
||||
print '<td class="center">';
|
||||
if ($type->active) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=deactivate&typeid='.$type->id.'&system='.$systemFilter.'&token='.newToken().'">'.img_picto($langs->trans('Enabled'), 'switch_on').'</a>';
|
||||
} else {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=activate&typeid='.$type->id.'&system='.$systemFilter.'&token='.newToken().'">'.img_picto($langs->trans('Disabled'), 'switch_off').'</a>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
print '<td class="center nowraponall">';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$type->id.'&system='.$systemFilter.'">'.img_edit().'</a>';
|
||||
if (!$type->is_system) {
|
||||
print ' <a href="'.$_SERVER['PHP_SELF'].'?action=delete&typeid='.$type->id.'&system='.$systemFilter.'" class="deletelink">'.img_delete().'</a>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
print '</tr>';
|
||||
}
|
||||
|
||||
if (empty($types)) {
|
||||
print '<tr class="oddeven"><td colspan="7" class="opacitymedium">'.$langs->trans('NoRecords').'</td></tr>';
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
}
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
// JavaScript for parent type selector
|
||||
print '<script>
|
||||
$(document).ready(function() {
|
||||
var $hidden = $("#allowed_parent_types");
|
||||
var $select = $("#parent_type_select");
|
||||
var $container = $("#selected_parent_types");
|
||||
|
||||
// Initialize from existing value
|
||||
function initSelected() {
|
||||
var val = $hidden.val();
|
||||
$container.empty();
|
||||
if (val) {
|
||||
var types = val.split(",");
|
||||
types.forEach(function(ref) {
|
||||
ref = ref.trim();
|
||||
if (ref) {
|
||||
var $opt = $select.find("option[value=\"" + ref + "\"]");
|
||||
var label = $opt.length ? $opt.data("label") : ref;
|
||||
addTag(ref, label);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add a tag to the list
|
||||
function addTag(ref, label) {
|
||||
var $tag = $("<span class=\"kundenkarte-tag\">");
|
||||
$tag.text(ref + (label ? " (" + label + ")" : ""));
|
||||
var $remove = $("<span class=\"kundenkarte-tag-remove\">×</span>");
|
||||
$remove.on("click", function() {
|
||||
$tag.remove();
|
||||
updateHidden();
|
||||
});
|
||||
$tag.append($remove);
|
||||
$container.append($tag);
|
||||
}
|
||||
|
||||
// Update hidden field from tags
|
||||
function updateHidden() {
|
||||
var refs = [];
|
||||
$container.find(".kundenkarte-tag").each(function() {
|
||||
var text = $(this).text();
|
||||
var ref = text.split(" (")[0].trim();
|
||||
refs.push(ref);
|
||||
});
|
||||
$hidden.val(refs.join(","));
|
||||
}
|
||||
|
||||
// Add button click
|
||||
$("#add_parent_type_btn").on("click", function() {
|
||||
var ref = $select.val();
|
||||
if (!ref) return;
|
||||
|
||||
// Check if already added
|
||||
var current = $hidden.val();
|
||||
if (current && current.split(",").indexOf(ref) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var label = $select.find("option:selected").data("label");
|
||||
addTag(ref, label);
|
||||
updateHidden();
|
||||
$select.val("");
|
||||
});
|
||||
|
||||
initSelected();
|
||||
});
|
||||
</script>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
160
admin/setup.php
Executable file
160
admin/setup.php
Executable file
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file kundenkarte/admin/setup.php
|
||||
* \ingroup kundenkarte
|
||||
* \brief KundenKarte setup page.
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../../main.inc.php")) {
|
||||
$res = @include "../../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
// Libraries
|
||||
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
|
||||
require_once '../lib/kundenkarte.lib.php';
|
||||
|
||||
// Translations
|
||||
$langs->loadLangs(array("admin", "kundenkarte@kundenkarte"));
|
||||
|
||||
// Access control
|
||||
if (!$user->admin && !$user->hasRight('kundenkarte', 'admin')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Parameters
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
if ($action == 'update') {
|
||||
$error = 0;
|
||||
|
||||
// Save settings
|
||||
$res = dolibarr_set_const($db, 'KUNDENKARTE_SHOW_FAVORITES_TAB', GETPOSTINT('KUNDENKARTE_SHOW_FAVORITES_TAB'), 'chaine', 0, '', $conf->entity);
|
||||
if (!($res > 0)) {
|
||||
$error++;
|
||||
}
|
||||
|
||||
$res = dolibarr_set_const($db, 'KUNDENKARTE_SHOW_ANLAGEN_TAB', GETPOSTINT('KUNDENKARTE_SHOW_ANLAGEN_TAB'), 'chaine', 0, '', $conf->entity);
|
||||
if (!($res > 0)) {
|
||||
$error++;
|
||||
}
|
||||
|
||||
$res = dolibarr_set_const($db, 'KUNDENKARTE_DEFAULT_ORDER_TYPE', GETPOSTINT('KUNDENKARTE_DEFAULT_ORDER_TYPE'), 'chaine', 0, '', $conf->entity);
|
||||
if (!($res > 0)) {
|
||||
$error++;
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($langs->trans("Error"), null, 'errors');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$form = new Form($db);
|
||||
|
||||
$title = $langs->trans("KundenKarteSetup");
|
||||
llxHeader('', $title);
|
||||
|
||||
// Subheader
|
||||
$linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
|
||||
print load_fiche_titre($title, $linkback, 'title_setup');
|
||||
|
||||
// Configuration header
|
||||
$head = kundenkarteAdminPrepareHead();
|
||||
print dol_get_fiche_head($head, 'settings', $langs->trans('ModuleKundenKarteName'), -1, "fa-address-card");
|
||||
|
||||
print '<span class="opacitymedium">'.$langs->trans("KundenKarteSetupPage").'</span><br><br>';
|
||||
|
||||
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="update">';
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
|
||||
// Header
|
||||
print '<tr class="liste_titre">';
|
||||
print '<td>'.$langs->trans("Parameter").'</td>';
|
||||
print '<td>'.$langs->trans("Value").'</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Show Favorites Tab
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("ShowFavoritesTab").'</td>';
|
||||
print '<td>';
|
||||
print $form->selectyesno('KUNDENKARTE_SHOW_FAVORITES_TAB', getDolGlobalInt('KUNDENKARTE_SHOW_FAVORITES_TAB', 1), 1);
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Show Anlagen Tab
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("ShowAnlagenTab").'</td>';
|
||||
print '<td>';
|
||||
print $form->selectyesno('KUNDENKARTE_SHOW_ANLAGEN_TAB', getDolGlobalInt('KUNDENKARTE_SHOW_ANLAGEN_TAB', 1), 1);
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Default Order Type for Favorites
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("DefaultOrderType").'</td>';
|
||||
print '<td>';
|
||||
$orderTypes = array(
|
||||
0 => $langs->trans("OrderTypeOrder"),
|
||||
1 => $langs->trans("OrderTypeProposal"),
|
||||
);
|
||||
print $form->selectarray('KUNDENKARTE_DEFAULT_ORDER_TYPE', $orderTypes, getDolGlobalInt('KUNDENKARTE_DEFAULT_ORDER_TYPE', 0));
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
print '<br>';
|
||||
print '<div class="center">';
|
||||
print '<input type="submit" class="button" value="'.$langs->trans("Save").'">';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
|
||||
// Info section
|
||||
print '<br>';
|
||||
print '<div class="info">';
|
||||
print '<strong>'.$langs->trans("ConfigurationHelp").':</strong><br>';
|
||||
print '• '.$langs->trans("ConfigHelpSystems").'<br>';
|
||||
print '• '.$langs->trans("ConfigHelpTypes").'<br>';
|
||||
print '</div>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
54
ajax/anlage_docs.php
Executable file
54
ajax/anlage_docs.php
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* AJAX endpoint to get documents for an installation element
|
||||
*/
|
||||
|
||||
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
||||
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
|
||||
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
||||
if (!defined('NOREQUIRESOC')) define('NOREQUIRESOC', '1');
|
||||
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
dol_include_once('/kundenkarte/class/anlagefile.class.php');
|
||||
dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$anlageId = GETPOSTINT('anlage_id');
|
||||
|
||||
if ($anlageId <= 0) {
|
||||
echo json_encode(array('error' => 'Invalid ID', 'docs' => array()));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get the anlage to know the socid
|
||||
$anlage = new Anlage($db);
|
||||
if ($anlage->fetch($anlageId) <= 0) {
|
||||
echo json_encode(array('error' => 'Element not found', 'docs' => array()));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get documents for this element (pdf and document types)
|
||||
$anlagefile = new AnlageFile($db);
|
||||
$files = $anlagefile->fetchAllByAnlage($anlageId);
|
||||
|
||||
$docs = array();
|
||||
foreach ($files as $file) {
|
||||
// Only include PDFs and documents, not images
|
||||
if (in_array($file->file_type, array('pdf', 'document'))) {
|
||||
$docs[] = array(
|
||||
'id' => $file->id,
|
||||
'name' => $file->filename,
|
||||
'url' => $file->getUrl(),
|
||||
'type' => $file->file_type,
|
||||
'icon' => $file->file_type == 'pdf' ? 'fa-file-pdf-o' : 'fa-file-text-o',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(array('docs' => $docs));
|
||||
50
ajax/anlage_images.php
Executable file
50
ajax/anlage_images.php
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* AJAX endpoint to get images for an installation element
|
||||
*/
|
||||
|
||||
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
||||
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
|
||||
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
||||
if (!defined('NOREQUIRESOC')) define('NOREQUIRESOC', '1');
|
||||
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
dol_include_once('/kundenkarte/class/anlagefile.class.php');
|
||||
dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$anlageId = GETPOSTINT('anlage_id');
|
||||
|
||||
if ($anlageId <= 0) {
|
||||
echo json_encode(array('error' => 'Invalid ID', 'images' => array()));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get the anlage to know the socid
|
||||
$anlage = new Anlage($db);
|
||||
if ($anlage->fetch($anlageId) <= 0) {
|
||||
echo json_encode(array('error' => 'Element not found', 'images' => array()));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get images for this element
|
||||
$anlagefile = new AnlageFile($db);
|
||||
$files = $anlagefile->fetchAllByAnlage($anlageId, 'image');
|
||||
|
||||
$images = array();
|
||||
foreach ($files as $file) {
|
||||
$images[] = array(
|
||||
'id' => $file->id,
|
||||
'name' => $file->filename,
|
||||
'url' => $file->getUrl(),
|
||||
'thumb' => $file->getThumbUrl() ?: $file->getUrl(),
|
||||
);
|
||||
}
|
||||
|
||||
echo json_encode(array('images' => $images));
|
||||
102
ajax/anlage_tooltip.php
Executable file
102
ajax/anlage_tooltip.php
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* AJAX handler for tooltip data
|
||||
*/
|
||||
|
||||
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
||||
if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1');
|
||||
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
||||
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||
dol_include_once('/kundenkarte/class/anlagetype.class.php');
|
||||
dol_include_once('/kundenkarte/class/anlagefile.class.php');
|
||||
|
||||
$langs->loadLangs(array('kundenkarte@kundenkarte'));
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$id = GETPOSTINT('id');
|
||||
|
||||
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Access denied'));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Invalid ID'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$anlage = new Anlage($db);
|
||||
$result = $anlage->fetch($id);
|
||||
|
||||
if ($result <= 0) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Element not found'));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get type and fields
|
||||
$type = new AnlageType($db);
|
||||
$type->fetch($anlage->fk_anlage_type);
|
||||
$typeFields = $type->fetchFields();
|
||||
|
||||
// Get field values
|
||||
$fieldValues = $anlage->getFieldValues();
|
||||
$fieldsData = array();
|
||||
|
||||
foreach ($typeFields as $field) {
|
||||
if ($field->show_in_hover) {
|
||||
$value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : '';
|
||||
|
||||
// For select fields, get label
|
||||
if ($field->field_type == 'select' && $value && $field->field_options) {
|
||||
$options = json_decode($field->field_options, true);
|
||||
if (isset($options['options'][$value])) {
|
||||
$value = $options['options'][$value];
|
||||
}
|
||||
}
|
||||
|
||||
$fieldsData[$field->field_code] = array(
|
||||
'label' => $field->field_label,
|
||||
'value' => $value,
|
||||
'show_in_hover' => true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Get images
|
||||
$anlagefile = new AnlageFile($db);
|
||||
$files = $anlagefile->fetchAllByAnlage($id, 'image');
|
||||
|
||||
$images = array();
|
||||
foreach ($files as $file) {
|
||||
$images[] = array(
|
||||
'id' => $file->id,
|
||||
'filename' => $file->filename,
|
||||
'url' => $file->getUrl(),
|
||||
'thumb_url' => $file->getThumbUrl() ?: $file->getUrl()
|
||||
);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'id' => $anlage->id,
|
||||
'label' => $anlage->label,
|
||||
'type_label' => $anlage->type_label,
|
||||
'picto' => $anlage->type_picto,
|
||||
'manufacturer' => $anlage->manufacturer,
|
||||
'model' => $anlage->model,
|
||||
'serial_number' => $anlage->serial_number,
|
||||
'power_rating' => $anlage->power_rating,
|
||||
'location' => $anlage->location,
|
||||
'fields' => $fieldsData,
|
||||
'images' => $images
|
||||
);
|
||||
|
||||
echo json_encode(array('success' => true, 'data' => $data));
|
||||
75
ajax/favorite_update.php
Normal file
75
ajax/favorite_update.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file ajax/favorite_update.php
|
||||
* \brief AJAX handler for updating favorite product quantity
|
||||
*/
|
||||
|
||||
if (!defined('NOTOKENRENEWAL')) {
|
||||
define('NOTOKENRENEWAL', '1');
|
||||
}
|
||||
if (!defined('NOREQUIREMENU')) {
|
||||
define('NOREQUIREMENU', '1');
|
||||
}
|
||||
if (!defined('NOREQUIREAJAX')) {
|
||||
define('NOREQUIREAJAX', '1');
|
||||
}
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../../main.inc.php")) $res = @include "../../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
dol_include_once('/kundenkarte/class/favoriteproduct.class.php');
|
||||
|
||||
// Check permission
|
||||
if (!$user->hasRight('kundenkarte', 'write')) {
|
||||
http_response_code(403);
|
||||
echo json_encode(array('error' => 'Permission denied'));
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$id = GETPOSTINT('id');
|
||||
$qty = GETPOSTFLOAT('qty');
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(array('error' => 'Invalid ID'));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($qty <= 0) {
|
||||
echo json_encode(array('error' => 'Invalid quantity'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$favoriteProduct = new FavoriteProduct($db);
|
||||
$result = $favoriteProduct->fetch($id);
|
||||
|
||||
if ($result <= 0) {
|
||||
echo json_encode(array('error' => 'Record not found'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$favoriteProduct->qty = $qty;
|
||||
$result = $favoriteProduct->update($user);
|
||||
|
||||
if ($result > 0) {
|
||||
echo json_encode(array(
|
||||
'success' => true,
|
||||
'id' => $id,
|
||||
'qty' => $qty
|
||||
));
|
||||
} else {
|
||||
echo json_encode(array('error' => $favoriteProduct->error));
|
||||
}
|
||||
145
ajax/icon_upload.php
Normal file
145
ajax/icon_upload.php
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* AJAX handler for custom icon upload
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) {
|
||||
http_response_code(500);
|
||||
die(json_encode(array('error' => 'Include of main fails')));
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Security check
|
||||
if (!$user->admin && !$user->hasRight('kundenkarte', 'admin')) {
|
||||
http_response_code(403);
|
||||
die(json_encode(array('error' => 'Access denied')));
|
||||
}
|
||||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
|
||||
// Directory for custom icons
|
||||
$iconDir = DOL_DATA_ROOT.'/kundenkarte/icons';
|
||||
$iconUrl = DOL_URL_ROOT.'/document.php?modulepart=kundenkarte&file=icons/';
|
||||
|
||||
// Create directory if not exists
|
||||
if (!is_dir($iconDir)) {
|
||||
dol_mkdir($iconDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all custom icons
|
||||
*/
|
||||
if ($action == 'list') {
|
||||
$icons = array();
|
||||
|
||||
if (is_dir($iconDir)) {
|
||||
$files = scandir($iconDir);
|
||||
foreach ($files as $file) {
|
||||
if ($file == '.' || $file == '..') continue;
|
||||
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
if (in_array($ext, array('png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'))) {
|
||||
$icons[] = array(
|
||||
'filename' => $file,
|
||||
'url' => $iconUrl.urlencode($file),
|
||||
'name' => pathinfo($file, PATHINFO_FILENAME)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(array('success' => true, 'icons' => $icons));
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a new icon
|
||||
*/
|
||||
if ($action == 'upload') {
|
||||
if (empty($_FILES['icon']) || $_FILES['icon']['error'] != 0) {
|
||||
http_response_code(400);
|
||||
die(json_encode(array('error' => 'No file uploaded or upload error')));
|
||||
}
|
||||
|
||||
$file = $_FILES['icon'];
|
||||
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
|
||||
// Check extension
|
||||
$allowedExt = array('png', 'jpg', 'jpeg', 'gif', 'svg', 'webp');
|
||||
if (!in_array($ext, $allowedExt)) {
|
||||
http_response_code(400);
|
||||
die(json_encode(array('error' => 'Invalid file type. Allowed: '.implode(', ', $allowedExt))));
|
||||
}
|
||||
|
||||
// Check size (max 500KB)
|
||||
if ($file['size'] > 512000) {
|
||||
http_response_code(400);
|
||||
die(json_encode(array('error' => 'File too large. Max 500KB')));
|
||||
}
|
||||
|
||||
// Sanitize filename
|
||||
$filename = dol_sanitizeFileName($file['name']);
|
||||
$filename = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', $filename);
|
||||
|
||||
// Check if file exists, add number if so
|
||||
$baseName = pathinfo($filename, PATHINFO_FILENAME);
|
||||
$counter = 1;
|
||||
while (file_exists($iconDir.'/'.$filename)) {
|
||||
$filename = $baseName.'_'.$counter.'.'.$ext;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
// Move file
|
||||
if (move_uploaded_file($file['tmp_name'], $iconDir.'/'.$filename)) {
|
||||
echo json_encode(array(
|
||||
'success' => true,
|
||||
'icon' => array(
|
||||
'filename' => $filename,
|
||||
'url' => $iconUrl.urlencode($filename),
|
||||
'name' => pathinfo($filename, PATHINFO_FILENAME)
|
||||
)
|
||||
));
|
||||
} else {
|
||||
http_response_code(500);
|
||||
die(json_encode(array('error' => 'Failed to save file')));
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an icon
|
||||
*/
|
||||
if ($action == 'delete') {
|
||||
$filename = GETPOST('filename', 'alphanohtml');
|
||||
|
||||
if (empty($filename)) {
|
||||
http_response_code(400);
|
||||
die(json_encode(array('error' => 'No filename provided')));
|
||||
}
|
||||
|
||||
// Sanitize to prevent directory traversal
|
||||
$filename = basename($filename);
|
||||
$filepath = $iconDir.'/'.$filename;
|
||||
|
||||
if (file_exists($filepath)) {
|
||||
if (unlink($filepath)) {
|
||||
echo json_encode(array('success' => true));
|
||||
} else {
|
||||
http_response_code(500);
|
||||
die(json_encode(array('error' => 'Failed to delete file')));
|
||||
}
|
||||
} else {
|
||||
http_response_code(404);
|
||||
die(json_encode(array('error' => 'File not found')));
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
die(json_encode(array('error' => 'Invalid action')));
|
||||
316
build/buildzip.php
Executable file
316
build/buildzip.php
Executable file
|
|
@ -0,0 +1,316 @@
|
|||
#!/usr/bin/env php -d memory_limit=256M
|
||||
<?php
|
||||
/**
|
||||
* buildzip.php
|
||||
*
|
||||
* Copyright (c) 2023-2025 Eric Seigne <eric.seigne@cap-rel.fr>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
The goal of that php CLI script is to make zip package of your module
|
||||
as an alternative to web "build zip" or "perl script makepack"
|
||||
*/
|
||||
|
||||
// ============================================= configuration
|
||||
|
||||
/**
|
||||
* list of files & dirs of your module
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
$listOfModuleContent = [
|
||||
'admin',
|
||||
'ajax',
|
||||
'backport',
|
||||
'class',
|
||||
'css',
|
||||
'COPYING',
|
||||
'core',
|
||||
'img',
|
||||
'js',
|
||||
'langs',
|
||||
'lib',
|
||||
'sql',
|
||||
'tpl',
|
||||
'*.md',
|
||||
'*.json',
|
||||
'*.php',
|
||||
'modulebuilder.txt',
|
||||
];
|
||||
|
||||
/**
|
||||
* if you want to exclude some files from your zip
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
$exclude_list = [
|
||||
'/^.git$/',
|
||||
'/.*js.map/',
|
||||
'/DEV.md/'
|
||||
];
|
||||
|
||||
// ============================================= end of configuration
|
||||
|
||||
/**
|
||||
* auto detect module name and version from file name
|
||||
*
|
||||
* @return (string|string)[] module name and module version
|
||||
*/
|
||||
function detectModule()
|
||||
{
|
||||
$name = $version = "";
|
||||
$tab = glob("core/modules/mod*.class.php");
|
||||
if (count($tab) == 0) {
|
||||
echo "[fail] Error on auto detect data : there is no mod*.class.php file into core/modules dir\n";
|
||||
exit(-1);
|
||||
}
|
||||
if (count($tab) == 1) {
|
||||
$file = $tab[0];
|
||||
$pattern = "/.*mod(?<mod>.*)\.class\.php/";
|
||||
if (preg_match_all($pattern, $file, $matches)) {
|
||||
$name = strtolower(reset($matches['mod']));
|
||||
}
|
||||
|
||||
echo "extract data from $file\n";
|
||||
if (!file_exists($file) || $name == "") {
|
||||
echo "[fail] Error on auto detect data\n";
|
||||
exit(-2);
|
||||
}
|
||||
} else {
|
||||
echo "[fail] Error there is more than one mod*.class.php file into core/modules dir\n";
|
||||
exit(-3);
|
||||
}
|
||||
|
||||
//extract version from file
|
||||
$contents = file_get_contents($file);
|
||||
$pattern = "/^.*this->version\s*=\s*'(?<version>.*)'\s*;.*\$/m";
|
||||
|
||||
// search, and store all matching occurrences in $matches
|
||||
if (preg_match_all($pattern, $contents, $matches)) {
|
||||
$version = reset($matches['version']);
|
||||
}
|
||||
|
||||
if (version_compare($version, '0.0.1', '>=') != 1) {
|
||||
echo "[fail] Error auto extract version fail\n";
|
||||
exit(-4);
|
||||
}
|
||||
|
||||
echo "module name = $name, version = $version\n";
|
||||
return [(string) $name, (string) $version];
|
||||
}
|
||||
|
||||
/**
|
||||
* delete recursively a directory
|
||||
*
|
||||
* @param string $dir dir path to delete
|
||||
*
|
||||
* @return bool true on success or false on failure.
|
||||
*/
|
||||
function delTree($dir)
|
||||
{
|
||||
$files = array_diff(scandir($dir), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
(is_dir("$dir/$file")) ? delTree("$dir/$file") : secureUnlink("$dir/$file");
|
||||
}
|
||||
return rmdir($dir);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* do a secure delete file/dir with double check
|
||||
* (don't trust unlink return)
|
||||
*
|
||||
* @param string $path full path to delete
|
||||
*
|
||||
* @return bool true on success ($path does not exists at the end of process), else exit
|
||||
*/
|
||||
function secureUnlink($path)
|
||||
{
|
||||
if (file_exists($path)) {
|
||||
if (unlink($path)) {
|
||||
//then check if really deleted
|
||||
clearstatcache();
|
||||
if (file_exists($path)) { // @phpstan-ignore-line
|
||||
echo "[fail] unlink of $path fail !\n";
|
||||
exit(-5);
|
||||
}
|
||||
} else {
|
||||
echo "[fail] unlink of $path fail !\n";
|
||||
exit(-6);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a directory and check if dir exists
|
||||
*
|
||||
* @param string $path path to make
|
||||
*
|
||||
* @return bool true on success ($path exists at the end of process), else exit
|
||||
*/
|
||||
function mkdirAndCheck($path)
|
||||
{
|
||||
if (mkdir($path)) {
|
||||
clearstatcache();
|
||||
if (is_dir($path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
echo "[fail] Error on $path (mkdir)\n";
|
||||
exit(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if that filename is concerned by exclude filter
|
||||
*
|
||||
* @param string $filename file name to check
|
||||
*
|
||||
* @return bool true if file is in excluded list
|
||||
*/
|
||||
function is_excluded($filename)
|
||||
{
|
||||
global $exclude_list;
|
||||
$count = 0;
|
||||
$notused = preg_filter($exclude_list, '1', $filename, -1, $count);
|
||||
if ($count > 0) {
|
||||
echo " - exclude $filename\n";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* recursive copy files & dirs
|
||||
*
|
||||
* @param string $src source dir
|
||||
* @param string $dst target dir
|
||||
*
|
||||
* @return bool true on success or false on failure.
|
||||
*/
|
||||
function rcopy($src, $dst)
|
||||
{
|
||||
if (is_dir($src)) {
|
||||
// Make the destination directory if not exist
|
||||
mkdirAndCheck($dst);
|
||||
// open the source directory
|
||||
$dir = opendir($src);
|
||||
|
||||
// Loop through the files in source directory
|
||||
while ($file = readdir($dir)) {
|
||||
if (($file != '.') && ($file != '..')) {
|
||||
if (is_dir($src . '/' . $file)) {
|
||||
// Recursively calling custom copy function
|
||||
// for sub directory
|
||||
if (!rcopy($src . '/' . $file, $dst . '/' . $file)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!is_excluded($file)) {
|
||||
if (!copy($src . '/' . $file, $dst . '/' . $file)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
} elseif (is_file($src)) {
|
||||
if (!is_excluded($src)) {
|
||||
if (!copy($src, $dst)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* build a zip file with only php code and no external depends
|
||||
* on "zip" exec for example
|
||||
*
|
||||
* @param string $folder folder to use as zip root
|
||||
* @param ZipArchive $zip zip object (ZipArchive)
|
||||
* @param string $root relative root path into the zip
|
||||
*
|
||||
* @return bool true on success or false on failure.
|
||||
*/
|
||||
function zipDir($folder, &$zip, $root = "")
|
||||
{
|
||||
foreach (new \DirectoryIterator($folder) as $f) {
|
||||
if ($f->isDot()) {
|
||||
continue;
|
||||
} //skip . ..
|
||||
$src = $folder . '/' . $f;
|
||||
$dst = substr($f->getPathname(), strlen($root));
|
||||
if ($f->isDir()) {
|
||||
if ($zip->addEmptyDir($dst)) {
|
||||
if (zipDir($src, $zip, $root)) {
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($f->isFile()) {
|
||||
if (! $zip->addFile($src, $dst)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* main part of script
|
||||
*/
|
||||
|
||||
list($mod, $version) = detectModule();
|
||||
$outzip = sys_get_temp_dir() . "/module_" . $mod . "-" . $version . ".zip";
|
||||
if (file_exists($outzip)) {
|
||||
secureUnlink($outzip);
|
||||
}
|
||||
|
||||
//copy all sources into system temp directory
|
||||
$tmpdir = tempnam(sys_get_temp_dir(), $mod . "-module");
|
||||
secureUnlink($tmpdir);
|
||||
mkdirAndCheck($tmpdir);
|
||||
$dst = $tmpdir . "/" . $mod;
|
||||
mkdirAndCheck($dst);
|
||||
|
||||
foreach ($listOfModuleContent as $moduleContent) {
|
||||
foreach (glob($moduleContent) as $entry) {
|
||||
if (!rcopy($entry, $dst . '/' . $entry)) {
|
||||
echo "[fail] Error on copy " . $entry . " to " . $dst . "/" . $entry . "\n";
|
||||
echo "Please take time to analyze the problem and fix the bug\n";
|
||||
exit(-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$z = new ZipArchive();
|
||||
$z->open($outzip, ZIPARCHIVE::CREATE);
|
||||
zipDir($tmpdir, $z, $tmpdir . '/');
|
||||
$z->close();
|
||||
delTree($tmpdir);
|
||||
if (file_exists($outzip)) {
|
||||
echo "[success] module archive is ready : $outzip ...\n";
|
||||
} else {
|
||||
echo "[fail] build zip error\n";
|
||||
exit(-9);
|
||||
}
|
||||
11
build/makepack-kundenkarte.conf
Executable file
11
build/makepack-kundenkarte.conf
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
# Your module name here
|
||||
#
|
||||
# Goal: Goal of module
|
||||
# Version: <version>
|
||||
# Author: Copyright <year> - <name of author>
|
||||
# License: GPLv3
|
||||
# Install: Just unpack content of module package in Dolibarr directory.
|
||||
# Setup: Go on Dolibarr setup - modules to enable module.
|
||||
#
|
||||
# Files in module
|
||||
mymodule/
|
||||
633
class/anlage.class.php
Executable file
633
class/anlage.class.php
Executable file
|
|
@ -0,0 +1,633 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class Anlage
|
||||
* Manages technical installation elements for customers
|
||||
*/
|
||||
class Anlage extends CommonObject
|
||||
{
|
||||
public $element = 'anlage';
|
||||
public $table_element = 'kundenkarte_anlage';
|
||||
|
||||
public $ref;
|
||||
public $label;
|
||||
public $fk_soc;
|
||||
public $fk_contact;
|
||||
public $fk_anlage_type;
|
||||
public $fk_parent;
|
||||
public $fk_system;
|
||||
|
||||
public $manufacturer;
|
||||
public $model;
|
||||
public $serial_number;
|
||||
public $power_rating;
|
||||
public $field_values;
|
||||
|
||||
public $location;
|
||||
public $installation_date;
|
||||
public $warranty_until;
|
||||
|
||||
public $rang;
|
||||
public $level;
|
||||
public $note_private;
|
||||
public $note_public;
|
||||
public $status;
|
||||
|
||||
public $date_creation;
|
||||
public $fk_user_creat;
|
||||
public $fk_user_modif;
|
||||
|
||||
// Loaded objects
|
||||
public $type;
|
||||
public $children = array();
|
||||
public $files = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DoliDB $db Database handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create object in database
|
||||
*
|
||||
* @param User $user User that creates
|
||||
* @param bool $notrigger false=launch triggers, true=disable triggers
|
||||
* @return int Return integer <0 if KO, Id of created object if OK
|
||||
*/
|
||||
public function create($user, $notrigger = false)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$error = 0;
|
||||
$now = dol_now();
|
||||
|
||||
// Check parameters
|
||||
if (empty($this->fk_soc) || empty($this->fk_anlage_type) || empty($this->label)) {
|
||||
$this->error = 'ErrorMissingParameters';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate level
|
||||
$this->level = 0;
|
||||
if ($this->fk_parent > 0) {
|
||||
$parent = new Anlage($this->db);
|
||||
if ($parent->fetch($this->fk_parent) > 0) {
|
||||
$this->level = $parent->level + 1;
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||
$sql .= "entity, ref, label, fk_soc, fk_contact, fk_anlage_type, fk_parent, fk_system,";
|
||||
$sql .= " manufacturer, model, serial_number, power_rating, field_values,";
|
||||
$sql .= " location, installation_date, warranty_until,";
|
||||
$sql .= " rang, level, note_private, note_public, status,";
|
||||
$sql .= " date_creation, fk_user_creat";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= ((int) $conf->entity);
|
||||
$sql .= ", ".($this->ref ? "'".$this->db->escape($this->ref)."'" : "NULL");
|
||||
$sql .= ", '".$this->db->escape($this->label)."'";
|
||||
$sql .= ", ".((int) $this->fk_soc);
|
||||
$sql .= ", ".($this->fk_contact > 0 ? ((int) $this->fk_contact) : "NULL");
|
||||
$sql .= ", ".((int) $this->fk_anlage_type);
|
||||
$sql .= ", ".((int) ($this->fk_parent > 0 ? $this->fk_parent : 0));
|
||||
$sql .= ", ".((int) $this->fk_system);
|
||||
$sql .= ", ".($this->manufacturer ? "'".$this->db->escape($this->manufacturer)."'" : "NULL");
|
||||
$sql .= ", ".($this->model ? "'".$this->db->escape($this->model)."'" : "NULL");
|
||||
$sql .= ", ".($this->serial_number ? "'".$this->db->escape($this->serial_number)."'" : "NULL");
|
||||
$sql .= ", ".($this->power_rating ? "'".$this->db->escape($this->power_rating)."'" : "NULL");
|
||||
$sql .= ", ".($this->field_values ? "'".$this->db->escape($this->field_values)."'" : "NULL");
|
||||
$sql .= ", ".($this->location ? "'".$this->db->escape($this->location)."'" : "NULL");
|
||||
$sql .= ", ".($this->installation_date ? "'".$this->db->idate($this->installation_date)."'" : "NULL");
|
||||
$sql .= ", ".($this->warranty_until ? "'".$this->db->idate($this->warranty_until)."'" : "NULL");
|
||||
$sql .= ", ".((int) $this->rang);
|
||||
$sql .= ", ".((int) $this->level);
|
||||
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
||||
$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
||||
$sql .= ", ".((int) ($this->status !== null ? $this->status : 1));
|
||||
$sql .= ", '".$this->db->idate($now)."'";
|
||||
$sql .= ", ".((int) $user->id);
|
||||
$sql .= ")";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
||||
$this->date_creation = $now;
|
||||
$this->fk_user_creat = $user->id;
|
||||
|
||||
// Create directory for files
|
||||
$this->createFileDirectory();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load object from database
|
||||
*
|
||||
* @param int $id ID of record
|
||||
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,";
|
||||
$sql .= " s.label as system_label, s.code as system_code";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage_type as t ON a.fk_anlage_type = t.rowid";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON a.fk_system = s.rowid";
|
||||
$sql .= " WHERE a.rowid = ".((int) $id);
|
||||
$sql .= " AND a.entity = ".((int) $conf->entity);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
if ($this->db->num_rows($resql)) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
$this->setFromObject($obj);
|
||||
$this->db->free($resql);
|
||||
return 1;
|
||||
} else {
|
||||
$this->db->free($resql);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set object properties from database object
|
||||
*
|
||||
* @param object $obj Database object
|
||||
*/
|
||||
private function setFromObject($obj)
|
||||
{
|
||||
$this->id = $obj->rowid;
|
||||
$this->entity = $obj->entity;
|
||||
$this->ref = $obj->ref;
|
||||
$this->label = $obj->label;
|
||||
$this->fk_soc = $obj->fk_soc;
|
||||
$this->fk_contact = isset($obj->fk_contact) ? $obj->fk_contact : null;
|
||||
$this->fk_anlage_type = $obj->fk_anlage_type;
|
||||
$this->fk_parent = $obj->fk_parent;
|
||||
$this->fk_system = $obj->fk_system;
|
||||
|
||||
$this->manufacturer = $obj->manufacturer;
|
||||
$this->model = $obj->model;
|
||||
$this->serial_number = $obj->serial_number;
|
||||
$this->power_rating = $obj->power_rating;
|
||||
$this->field_values = $obj->field_values;
|
||||
|
||||
$this->location = $obj->location;
|
||||
$this->installation_date = $this->db->jdate($obj->installation_date);
|
||||
$this->warranty_until = $this->db->jdate($obj->warranty_until);
|
||||
|
||||
$this->rang = $obj->rang;
|
||||
$this->level = $obj->level;
|
||||
$this->note_private = $obj->note_private;
|
||||
$this->note_public = $obj->note_public;
|
||||
$this->status = $obj->status;
|
||||
|
||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||
$this->tms = $this->db->jdate($obj->tms);
|
||||
$this->fk_user_creat = $obj->fk_user_creat;
|
||||
$this->fk_user_modif = $obj->fk_user_modif;
|
||||
|
||||
// Type info
|
||||
$this->type_label = $obj->type_label;
|
||||
$this->type_short = $obj->type_short;
|
||||
$this->type_picto = $obj->type_picto;
|
||||
|
||||
// System info
|
||||
$this->system_label = $obj->system_label;
|
||||
$this->system_code = $obj->system_code;
|
||||
|
||||
// File counts (from tree query)
|
||||
$this->image_count = isset($obj->image_count) ? (int) $obj->image_count : 0;
|
||||
$this->doc_count = isset($obj->doc_count) ? (int) $obj->doc_count : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update object in database
|
||||
*
|
||||
* @param User $user User that modifies
|
||||
* @param bool $notrigger false=launch triggers, true=disable triggers
|
||||
* @return int Return integer <0 if KO, >0 if OK
|
||||
*/
|
||||
public function update($user, $notrigger = false)
|
||||
{
|
||||
$error = 0;
|
||||
|
||||
// Recalculate level if parent changed
|
||||
$this->level = 0;
|
||||
if ($this->fk_parent > 0) {
|
||||
$parent = new Anlage($this->db);
|
||||
if ($parent->fetch($this->fk_parent) > 0) {
|
||||
$this->level = $parent->level + 1;
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||
$sql .= " ref = ".($this->ref ? "'".$this->db->escape($this->ref)."'" : "NULL");
|
||||
$sql .= ", label = '".$this->db->escape($this->label)."'";
|
||||
$sql .= ", fk_anlage_type = ".((int) $this->fk_anlage_type);
|
||||
$sql .= ", fk_parent = ".((int) ($this->fk_parent > 0 ? $this->fk_parent : 0));
|
||||
$sql .= ", fk_system = ".((int) $this->fk_system);
|
||||
$sql .= ", manufacturer = ".($this->manufacturer ? "'".$this->db->escape($this->manufacturer)."'" : "NULL");
|
||||
$sql .= ", model = ".($this->model ? "'".$this->db->escape($this->model)."'" : "NULL");
|
||||
$sql .= ", serial_number = ".($this->serial_number ? "'".$this->db->escape($this->serial_number)."'" : "NULL");
|
||||
$sql .= ", power_rating = ".($this->power_rating ? "'".$this->db->escape($this->power_rating)."'" : "NULL");
|
||||
$sql .= ", field_values = ".($this->field_values ? "'".$this->db->escape($this->field_values)."'" : "NULL");
|
||||
$sql .= ", location = ".($this->location ? "'".$this->db->escape($this->location)."'" : "NULL");
|
||||
$sql .= ", installation_date = ".($this->installation_date ? "'".$this->db->idate($this->installation_date)."'" : "NULL");
|
||||
$sql .= ", warranty_until = ".($this->warranty_until ? "'".$this->db->idate($this->warranty_until)."'" : "NULL");
|
||||
$sql .= ", rang = ".((int) $this->rang);
|
||||
$sql .= ", level = ".((int) $this->level);
|
||||
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
||||
$sql .= ", note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
||||
$sql .= ", status = ".((int) $this->status);
|
||||
$sql .= ", fk_user_modif = ".((int) $user->id);
|
||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete object in database
|
||||
*
|
||||
* @param User $user User that deletes
|
||||
* @param bool $notrigger false=launch triggers, true=disable triggers
|
||||
* @return int Return integer <0 if KO, >0 if OK
|
||||
*/
|
||||
public function delete($user, $notrigger = false)
|
||||
{
|
||||
$error = 0;
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
// First delete all children recursively
|
||||
$children = $this->fetchChildren($this->id);
|
||||
foreach ($children as $child) {
|
||||
$childObj = new Anlage($this->db);
|
||||
if ($childObj->fetch($child->id) > 0) {
|
||||
$result = $childObj->delete($user, $notrigger);
|
||||
if ($result < 0) {
|
||||
$error++;
|
||||
$this->errors = array_merge($this->errors, $childObj->errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
// Delete files
|
||||
$this->deleteFileDirectory();
|
||||
|
||||
// Delete file records
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files WHERE fk_anlage = ".((int) $this->id);
|
||||
$this->db->query($sql);
|
||||
|
||||
// Delete the element
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch children of an element
|
||||
*
|
||||
* @param int $parentId Parent ID (0 for root elements)
|
||||
* @param int $socid Customer ID (required for root elements)
|
||||
* @param int $systemId System ID (optional filter)
|
||||
* @return array Array of Anlage objects
|
||||
*/
|
||||
public function fetchChildren($parentId = 0, $socid = 0, $systemId = 0)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$results = array();
|
||||
|
||||
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,";
|
||||
$sql .= " s.label as system_label, s.code as system_code,";
|
||||
// Count images
|
||||
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,";
|
||||
// Count documents (pdf + document)
|
||||
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type IN ('pdf', 'document')) as doc_count";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage_type as t ON a.fk_anlage_type = t.rowid";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON a.fk_system = s.rowid";
|
||||
$sql .= " WHERE a.fk_parent = ".((int) $parentId);
|
||||
$sql .= " AND a.entity = ".((int) $conf->entity);
|
||||
$sql .= " AND a.status = 1";
|
||||
|
||||
if ($parentId == 0 && $socid > 0) {
|
||||
$sql .= " AND a.fk_soc = ".((int) $socid);
|
||||
// Only show elements without contact assignment (thirdparty-level)
|
||||
$sql .= " AND (a.fk_contact IS NULL OR a.fk_contact = 0)";
|
||||
}
|
||||
if ($systemId > 0) {
|
||||
$sql .= " AND a.fk_system = ".((int) $systemId);
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY a.rang ASC, a.label ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$anlage = new Anlage($this->db);
|
||||
$anlage->setFromObject($obj);
|
||||
$results[] = $anlage;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch full tree for a customer and system
|
||||
*
|
||||
* @param int $socid Customer ID
|
||||
* @param int $systemId System ID
|
||||
* @return array Tree structure
|
||||
*/
|
||||
public function fetchTree($socid, $systemId)
|
||||
{
|
||||
$tree = array();
|
||||
$roots = $this->fetchChildren(0, $socid, $systemId);
|
||||
|
||||
foreach ($roots as $root) {
|
||||
$root->children = $this->fetchChildrenRecursive($root->id);
|
||||
$tree[] = $root;
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch children recursively
|
||||
*
|
||||
* @param int $parentId Parent ID
|
||||
* @return array Array of Anlage objects with children
|
||||
*/
|
||||
private function fetchChildrenRecursive($parentId)
|
||||
{
|
||||
$children = $this->fetchChildren($parentId);
|
||||
foreach ($children as $child) {
|
||||
$child->children = $this->fetchChildrenRecursive($child->id);
|
||||
}
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file storage directory path
|
||||
*
|
||||
* @return string Directory path
|
||||
*/
|
||||
public function getFileDirectory()
|
||||
{
|
||||
global $conf;
|
||||
return $conf->kundenkarte->dir_output.'/anlagen/'.$this->fk_soc.'/'.$this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the file directory
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function createFileDirectory()
|
||||
{
|
||||
$dir = $this->getFileDirectory();
|
||||
if (!is_dir($dir)) {
|
||||
return dol_mkdir($dir);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the file directory
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteFileDirectory()
|
||||
{
|
||||
$dir = $this->getFileDirectory();
|
||||
if (is_dir($dir)) {
|
||||
return dol_delete_dir_recursive($dir);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get decoded field values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFieldValues()
|
||||
{
|
||||
if (empty($this->field_values)) {
|
||||
return array();
|
||||
}
|
||||
$values = json_decode($this->field_values, true);
|
||||
return is_array($values) ? $values : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field values from array
|
||||
*
|
||||
* @param array $values Field values
|
||||
*/
|
||||
public function setFieldValues($values)
|
||||
{
|
||||
$this->field_values = json_encode($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific field value
|
||||
*
|
||||
* @param string $fieldCode Field code
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getFieldValue($fieldCode)
|
||||
{
|
||||
$values = $this->getFieldValues();
|
||||
return isset($values[$fieldCode]) ? $values[$fieldCode] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info for tree display
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTreeInfo()
|
||||
{
|
||||
$info = array();
|
||||
|
||||
// Get type fields that should show in tree
|
||||
$sql = "SELECT field_code, field_label FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field";
|
||||
$sql .= " WHERE fk_anlage_type = ".((int) $this->fk_anlage_type);
|
||||
$sql .= " AND show_in_tree = 1 AND active = 1";
|
||||
$sql .= " ORDER BY position ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$values = $this->getFieldValues();
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
if (isset($values[$obj->field_code]) && $values[$obj->field_code] !== '') {
|
||||
$info[] = $values[$obj->field_code];
|
||||
}
|
||||
}
|
||||
$this->db->free($resql);
|
||||
}
|
||||
|
||||
// Add common fields
|
||||
if ($this->manufacturer) {
|
||||
$info[] = $this->manufacturer;
|
||||
}
|
||||
if ($this->power_rating) {
|
||||
$info[] = $this->power_rating;
|
||||
}
|
||||
|
||||
return implode(', ', $info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch full tree for a contact and system
|
||||
*
|
||||
* @param int $socid Customer ID
|
||||
* @param int $contactid Contact ID
|
||||
* @param int $systemId System ID
|
||||
* @return array Tree structure
|
||||
*/
|
||||
public function fetchTreeByContact($socid, $contactid, $systemId)
|
||||
{
|
||||
$tree = array();
|
||||
$roots = $this->fetchChildrenByContact(0, $socid, $contactid, $systemId);
|
||||
|
||||
foreach ($roots as $root) {
|
||||
$root->children = $this->fetchChildrenByContactRecursive($root->id, $socid, $contactid);
|
||||
$tree[] = $root;
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch children of an element filtered by contact
|
||||
*
|
||||
* @param int $parentId Parent ID (0 for root elements)
|
||||
* @param int $socid Customer ID
|
||||
* @param int $contactid Contact ID
|
||||
* @param int $systemId System ID (optional filter)
|
||||
* @return array Array of Anlage objects
|
||||
*/
|
||||
public function fetchChildrenByContact($parentId = 0, $socid = 0, $contactid = 0, $systemId = 0)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$results = array();
|
||||
|
||||
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,";
|
||||
$sql .= " s.label as system_label, s.code as system_code,";
|
||||
// Count images
|
||||
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,";
|
||||
// Count documents (pdf + document)
|
||||
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type IN ('pdf', 'document')) as doc_count";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage_type as t ON a.fk_anlage_type = t.rowid";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON a.fk_system = s.rowid";
|
||||
$sql .= " WHERE a.fk_parent = ".((int) $parentId);
|
||||
$sql .= " AND a.entity = ".((int) $conf->entity);
|
||||
$sql .= " AND a.status = 1";
|
||||
|
||||
if ($parentId == 0) {
|
||||
$sql .= " AND a.fk_soc = ".((int) $socid);
|
||||
$sql .= " AND a.fk_contact = ".((int) $contactid);
|
||||
}
|
||||
if ($systemId > 0) {
|
||||
$sql .= " AND a.fk_system = ".((int) $systemId);
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY a.rang ASC, a.label ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$anlage = new Anlage($this->db);
|
||||
$anlage->setFromObject($obj);
|
||||
$results[] = $anlage;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch children recursively for contact
|
||||
*
|
||||
* @param int $parentId Parent ID
|
||||
* @param int $socid Customer ID
|
||||
* @param int $contactid Contact ID
|
||||
* @return array Array of Anlage objects with children
|
||||
*/
|
||||
private function fetchChildrenByContactRecursive($parentId, $socid, $contactid)
|
||||
{
|
||||
$children = $this->fetchChildrenByContact($parentId, $socid, $contactid);
|
||||
foreach ($children as $child) {
|
||||
$child->children = $this->fetchChildrenByContactRecursive($child->id, $socid, $contactid);
|
||||
}
|
||||
return $children;
|
||||
}
|
||||
}
|
||||
417
class/anlagefile.class.php
Executable file
417
class/anlagefile.class.php
Executable file
|
|
@ -0,0 +1,417 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class AnlageFile
|
||||
* Manages file attachments for installation elements
|
||||
*/
|
||||
class AnlageFile extends CommonObject
|
||||
{
|
||||
public $element = 'anlagefile';
|
||||
public $table_element = 'kundenkarte_anlage_files';
|
||||
|
||||
public $fk_anlage;
|
||||
public $filename;
|
||||
public $filepath;
|
||||
public $filesize;
|
||||
public $mimetype;
|
||||
public $file_type;
|
||||
public $label;
|
||||
public $description;
|
||||
public $is_cover;
|
||||
public $position;
|
||||
public $share;
|
||||
|
||||
public $date_creation;
|
||||
public $fk_user_creat;
|
||||
public $fk_user_modif;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DoliDB $db Database handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create object in database
|
||||
*
|
||||
* @param User $user User that creates
|
||||
* @return int Return integer <0 if KO, Id of created object if OK
|
||||
*/
|
||||
public function create($user)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$error = 0;
|
||||
$now = dol_now();
|
||||
|
||||
if (empty($this->fk_anlage) || empty($this->filename) || empty($this->filepath)) {
|
||||
$this->error = 'ErrorMissingParameters';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Determine file type
|
||||
if (empty($this->file_type)) {
|
||||
$this->file_type = $this->determineFileType($this->mimetype, $this->filename);
|
||||
}
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||
$sql .= "entity, fk_anlage, filename, filepath, filesize, mimetype,";
|
||||
$sql .= " file_type, label, description, is_cover, position, share,";
|
||||
$sql .= " date_creation, fk_user_creat";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= ((int) $conf->entity);
|
||||
$sql .= ", ".((int) $this->fk_anlage);
|
||||
$sql .= ", '".$this->db->escape($this->filename)."'";
|
||||
$sql .= ", '".$this->db->escape($this->filepath)."'";
|
||||
$sql .= ", ".((int) $this->filesize);
|
||||
$sql .= ", ".($this->mimetype ? "'".$this->db->escape($this->mimetype)."'" : "NULL");
|
||||
$sql .= ", '".$this->db->escape($this->file_type)."'";
|
||||
$sql .= ", ".($this->label ? "'".$this->db->escape($this->label)."'" : "NULL");
|
||||
$sql .= ", ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
|
||||
$sql .= ", ".((int) $this->is_cover);
|
||||
$sql .= ", ".((int) $this->position);
|
||||
$sql .= ", ".($this->share ? "'".$this->db->escape($this->share)."'" : "NULL");
|
||||
$sql .= ", '".$this->db->idate($now)."'";
|
||||
$sql .= ", ".((int) $user->id);
|
||||
$sql .= ")";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load object from database
|
||||
*
|
||||
* @param int $id ID of record
|
||||
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sql .= " WHERE rowid = ".((int) $id);
|
||||
$sql .= " AND entity = ".((int) $conf->entity);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
if ($this->db->num_rows($resql)) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
|
||||
$this->id = $obj->rowid;
|
||||
$this->entity = $obj->entity;
|
||||
$this->fk_anlage = $obj->fk_anlage;
|
||||
$this->filename = $obj->filename;
|
||||
$this->filepath = $obj->filepath;
|
||||
$this->filesize = $obj->filesize;
|
||||
$this->mimetype = $obj->mimetype;
|
||||
$this->file_type = $obj->file_type;
|
||||
$this->label = $obj->label;
|
||||
$this->description = $obj->description;
|
||||
$this->is_cover = $obj->is_cover;
|
||||
$this->position = $obj->position;
|
||||
$this->share = $obj->share;
|
||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||
$this->fk_user_creat = $obj->fk_user_creat;
|
||||
$this->fk_user_modif = $obj->fk_user_modif;
|
||||
|
||||
$this->db->free($resql);
|
||||
return 1;
|
||||
} else {
|
||||
$this->db->free($resql);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete object in database and file from disk
|
||||
*
|
||||
* @param User $user User that deletes
|
||||
* @return int Return integer <0 if KO, >0 if OK
|
||||
*/
|
||||
public function delete($user)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$error = 0;
|
||||
|
||||
// Delete physical file (use clean filepath)
|
||||
$fullpath = $this->getFullPath();
|
||||
if (file_exists($fullpath)) {
|
||||
dol_delete_file($fullpath);
|
||||
|
||||
// Delete thumbnail if exists
|
||||
$thumbpath = dirname($fullpath).'/thumbs/'.pathinfo($this->filename, PATHINFO_FILENAME).'_small.'.pathinfo($this->filename, PATHINFO_EXTENSION);
|
||||
if (file_exists($thumbpath)) {
|
||||
dol_delete_file($thumbpath);
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all files for an installation element
|
||||
*
|
||||
* @param int $anlageId Installation element ID
|
||||
* @param string $fileType Filter by file type (image, pdf, document, other)
|
||||
* @return array Array of AnlageFile objects
|
||||
*/
|
||||
public function fetchAllByAnlage($anlageId, $fileType = '')
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$results = array();
|
||||
|
||||
$sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sql .= " WHERE fk_anlage = ".((int) $anlageId);
|
||||
$sql .= " AND entity = ".((int) $conf->entity);
|
||||
if ($fileType) {
|
||||
$sql .= " AND file_type = '".$this->db->escape($fileType)."'";
|
||||
}
|
||||
$sql .= " ORDER BY is_cover DESC, position ASC, filename ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$file = new AnlageFile($this->db);
|
||||
$file->id = $obj->rowid;
|
||||
$file->fk_anlage = $obj->fk_anlage;
|
||||
$file->filename = $obj->filename;
|
||||
$file->filepath = $obj->filepath;
|
||||
$file->filesize = $obj->filesize;
|
||||
$file->mimetype = $obj->mimetype;
|
||||
$file->file_type = $obj->file_type;
|
||||
$file->label = $obj->label;
|
||||
$file->description = $obj->description;
|
||||
$file->is_cover = $obj->is_cover;
|
||||
$file->position = $obj->position;
|
||||
$file->share = $obj->share;
|
||||
$file->date_creation = $this->db->jdate($obj->date_creation);
|
||||
|
||||
$results[] = $file;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine file type from mimetype and filename
|
||||
*
|
||||
* @param string $mimetype MIME type
|
||||
* @param string $filename Filename
|
||||
* @return string File type (image, pdf, document, other)
|
||||
*/
|
||||
public function determineFileType($mimetype, $filename)
|
||||
{
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
|
||||
if (in_array($ext, array('jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp')) || strpos($mimetype, 'image/') === 0) {
|
||||
return 'image';
|
||||
}
|
||||
if ($ext == 'pdf' || $mimetype == 'application/pdf') {
|
||||
return 'pdf';
|
||||
}
|
||||
if (in_array($ext, array('doc', 'docx', 'xls', 'xlsx', 'odt', 'ods', 'txt', 'rtf'))) {
|
||||
return 'document';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full file path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFullPath()
|
||||
{
|
||||
global $conf;
|
||||
return $conf->kundenkarte->dir_output.'/'.$this->getCleanFilepath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL to view/download the file
|
||||
*
|
||||
* @param bool $forceDownload If true, force download instead of inline display
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl($forceDownload = false)
|
||||
{
|
||||
// Sanitize filepath - ensure it's relative, not absolute
|
||||
$filepath = $this->getCleanFilepath();
|
||||
$url = DOL_URL_ROOT.'/document.php?modulepart=kundenkarte&file='.urlencode($filepath);
|
||||
|
||||
// Add attachment=0 for inline display (especially for PDFs)
|
||||
// If forceDownload is true, don't add this parameter (default Dolibarr behavior is download)
|
||||
if (!$forceDownload) {
|
||||
$url .= '&attachment=0';
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clean relative filepath (fixes bad data)
|
||||
*
|
||||
* @return string Clean relative filepath
|
||||
*/
|
||||
public function getCleanFilepath()
|
||||
{
|
||||
$filepath = $this->filepath;
|
||||
|
||||
// If filepath contains "anlagen/" extract from there
|
||||
if (strpos($filepath, 'anlagen/') !== false) {
|
||||
$filepath = substr($filepath, strpos($filepath, 'anlagen/'));
|
||||
}
|
||||
// If it's still an absolute path (starts with / or file:// or Windows drive), just use filename
|
||||
elseif (preg_match('/^(\/|file:\/\/|[A-Za-z]:)/', $filepath)) {
|
||||
// Try to get just the filename and rebuild proper path
|
||||
$filename = basename($filepath);
|
||||
// Use fk_anlage to build correct path if available
|
||||
if ($this->fk_anlage > 0) {
|
||||
global $db;
|
||||
$sql = "SELECT fk_soc FROM ".MAIN_DB_PREFIX."kundenkarte_anlage WHERE rowid = ".((int) $this->fk_anlage);
|
||||
$resql = $db->query($sql);
|
||||
if ($resql && $db->num_rows($resql)) {
|
||||
$obj = $db->fetch_object($resql);
|
||||
$filepath = 'anlagen/'.$obj->fk_soc.'/'.$this->fk_anlage.'/'.$filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thumbnail URL for images
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getThumbUrl()
|
||||
{
|
||||
if ($this->file_type != 'image') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$filepath = $this->getCleanFilepath();
|
||||
$dir = dirname($filepath);
|
||||
$filename = pathinfo($this->filename, PATHINFO_FILENAME);
|
||||
$ext = pathinfo($this->filename, PATHINFO_EXTENSION);
|
||||
|
||||
$thumbpath = $dir.'/thumbs/'.$filename.'_small.'.$ext;
|
||||
|
||||
// Use attachment=0 for inline display
|
||||
return DOL_URL_ROOT.'/document.php?modulepart=kundenkarte&file='.urlencode($thumbpath).'&attachment=0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this file as cover image
|
||||
*
|
||||
* @param User $user User making the change
|
||||
* @return int <0 if KO, >0 if OK
|
||||
*/
|
||||
public function setAsCover($user)
|
||||
{
|
||||
$this->db->begin();
|
||||
|
||||
// Unset other covers for this anlage
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET is_cover = 0";
|
||||
$sql .= " WHERE fk_anlage = ".((int) $this->fk_anlage);
|
||||
$this->db->query($sql);
|
||||
|
||||
// Set this as cover
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET is_cover = 1, fk_user_modif = ".((int) $user->id);
|
||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||
$resql = $this->db->query($sql);
|
||||
|
||||
if ($resql) {
|
||||
$this->is_cover = 1;
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
} else {
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate thumbnail for image
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function generateThumbnail()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if ($this->file_type != 'image') {
|
||||
return false;
|
||||
}
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
|
||||
|
||||
$fullpath = $this->getFullPath();
|
||||
if (!file_exists($fullpath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$thumbdir = dirname($fullpath).'/thumbs';
|
||||
if (!is_dir($thumbdir)) {
|
||||
dol_mkdir($thumbdir);
|
||||
}
|
||||
|
||||
// Generate small thumbnail
|
||||
$result = vignette($fullpath, 200, 160, '_small', 80, 'thumbs');
|
||||
|
||||
return ($result !== false);
|
||||
}
|
||||
}
|
||||
369
class/anlagetype.class.php
Executable file
369
class/anlagetype.class.php
Executable file
|
|
@ -0,0 +1,369 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class AnlageType
|
||||
* Manages element type templates
|
||||
*/
|
||||
class AnlageType extends CommonObject
|
||||
{
|
||||
public $element = 'anlagetype';
|
||||
public $table_element = 'kundenkarte_anlage_type';
|
||||
|
||||
public $ref;
|
||||
public $label;
|
||||
public $label_short;
|
||||
public $description;
|
||||
public $fk_system;
|
||||
|
||||
public $can_have_children;
|
||||
public $can_be_nested;
|
||||
public $allowed_parent_types;
|
||||
|
||||
public $picto;
|
||||
public $color;
|
||||
public $is_system;
|
||||
public $position;
|
||||
public $active;
|
||||
|
||||
public $date_creation;
|
||||
public $fk_user_creat;
|
||||
public $fk_user_modif;
|
||||
|
||||
// Loaded objects
|
||||
public $system;
|
||||
public $fields = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DoliDB $db Database handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create object in database
|
||||
*
|
||||
* @param User $user User that creates
|
||||
* @return int Return integer <0 if KO, Id of created object if OK
|
||||
*/
|
||||
public function create($user)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$error = 0;
|
||||
$now = dol_now();
|
||||
|
||||
if (empty($this->ref) || empty($this->label) || empty($this->fk_system)) {
|
||||
$this->error = 'ErrorMissingParameters';
|
||||
return -1;
|
||||
}
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||
$sql .= "entity, ref, label, label_short, description, fk_system,";
|
||||
$sql .= " can_have_children, can_be_nested, allowed_parent_types,";
|
||||
$sql .= " picto, color, is_system, position, active,";
|
||||
$sql .= " date_creation, fk_user_creat";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= "0"; // entity 0 = global
|
||||
$sql .= ", '".$this->db->escape($this->ref)."'";
|
||||
$sql .= ", '".$this->db->escape($this->label)."'";
|
||||
$sql .= ", ".($this->label_short ? "'".$this->db->escape($this->label_short)."'" : "NULL");
|
||||
$sql .= ", ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
|
||||
$sql .= ", ".((int) $this->fk_system);
|
||||
$sql .= ", ".((int) $this->can_have_children);
|
||||
$sql .= ", ".((int) $this->can_be_nested);
|
||||
$sql .= ", ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL");
|
||||
$sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
||||
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||
$sql .= ", 0"; // is_system = 0 for user-created
|
||||
$sql .= ", ".((int) $this->position);
|
||||
$sql .= ", ".((int) ($this->active !== null ? $this->active : 1));
|
||||
$sql .= ", '".$this->db->idate($now)."'";
|
||||
$sql .= ", ".((int) $user->id);
|
||||
$sql .= ")";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load object from database
|
||||
*
|
||||
* @param int $id ID of record
|
||||
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
$sql = "SELECT t.*, s.label as system_label, s.code as system_code";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON t.fk_system = s.rowid";
|
||||
$sql .= " WHERE t.rowid = ".((int) $id);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
if ($this->db->num_rows($resql)) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
|
||||
$this->id = $obj->rowid;
|
||||
$this->entity = $obj->entity;
|
||||
$this->ref = $obj->ref;
|
||||
$this->label = $obj->label;
|
||||
$this->label_short = $obj->label_short;
|
||||
$this->description = $obj->description;
|
||||
$this->fk_system = $obj->fk_system;
|
||||
$this->can_have_children = $obj->can_have_children;
|
||||
$this->can_be_nested = $obj->can_be_nested;
|
||||
$this->allowed_parent_types = $obj->allowed_parent_types;
|
||||
$this->picto = $obj->picto;
|
||||
$this->color = $obj->color;
|
||||
$this->is_system = $obj->is_system;
|
||||
$this->position = $obj->position;
|
||||
$this->active = $obj->active;
|
||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||
$this->fk_user_creat = $obj->fk_user_creat;
|
||||
$this->fk_user_modif = $obj->fk_user_modif;
|
||||
|
||||
$this->system_label = $obj->system_label;
|
||||
$this->system_code = $obj->system_code;
|
||||
|
||||
$this->db->free($resql);
|
||||
return 1;
|
||||
} else {
|
||||
$this->db->free($resql);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update object in database
|
||||
*
|
||||
* @param User $user User that modifies
|
||||
* @return int Return integer <0 if KO, >0 if OK
|
||||
*/
|
||||
public function update($user)
|
||||
{
|
||||
$error = 0;
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||
$sql .= " ref = '".$this->db->escape($this->ref)."'";
|
||||
$sql .= ", label = '".$this->db->escape($this->label)."'";
|
||||
$sql .= ", label_short = ".($this->label_short ? "'".$this->db->escape($this->label_short)."'" : "NULL");
|
||||
$sql .= ", description = ".($this->description ? "'".$this->db->escape($this->description)."'" : "NULL");
|
||||
$sql .= ", fk_system = ".((int) $this->fk_system);
|
||||
$sql .= ", can_have_children = ".((int) $this->can_have_children);
|
||||
$sql .= ", can_be_nested = ".((int) $this->can_be_nested);
|
||||
$sql .= ", allowed_parent_types = ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL");
|
||||
$sql .= ", picto = ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
||||
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
|
||||
$sql .= ", position = ".((int) $this->position);
|
||||
$sql .= ", active = ".((int) $this->active);
|
||||
$sql .= ", fk_user_modif = ".((int) $user->id);
|
||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete object in database
|
||||
*
|
||||
* @param User $user User that deletes
|
||||
* @return int Return integer <0 if KO, >0 if OK
|
||||
*/
|
||||
public function delete($user)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
// Check if type is in use
|
||||
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."kundenkarte_anlage";
|
||||
$sql .= " WHERE fk_anlage_type = ".((int) $this->id);
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
if ($obj->cnt > 0) {
|
||||
$this->error = 'ErrorTypeInUse';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot delete system types
|
||||
if ($this->is_system) {
|
||||
$this->error = 'ErrorCannotDeleteSystemType';
|
||||
return -2;
|
||||
}
|
||||
|
||||
$error = 0;
|
||||
$this->db->begin();
|
||||
|
||||
// Delete fields first
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field WHERE fk_anlage_type = ".((int) $this->id);
|
||||
$this->db->query($sql);
|
||||
|
||||
// Delete type
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all types for a system
|
||||
*
|
||||
* @param int $systemId System ID (0 = all)
|
||||
* @param int $activeOnly Only active types
|
||||
* @return array Array of AnlageType objects
|
||||
*/
|
||||
public function fetchAllBySystem($systemId = 0, $activeOnly = 1)
|
||||
{
|
||||
$results = array();
|
||||
|
||||
$sql = "SELECT t.*, s.label as system_label, s.code as system_code";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON t.fk_system = s.rowid";
|
||||
$sql .= " WHERE 1 = 1";
|
||||
if ($systemId > 0) {
|
||||
$sql .= " AND t.fk_system = ".((int) $systemId);
|
||||
}
|
||||
if ($activeOnly) {
|
||||
$sql .= " AND t.active = 1";
|
||||
}
|
||||
$sql .= " ORDER BY t.fk_system ASC, t.position ASC, t.label ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$type = new AnlageType($this->db);
|
||||
$type->id = $obj->rowid;
|
||||
$type->ref = $obj->ref;
|
||||
$type->label = $obj->label;
|
||||
$type->label_short = $obj->label_short;
|
||||
$type->fk_system = $obj->fk_system;
|
||||
$type->can_have_children = $obj->can_have_children;
|
||||
$type->can_be_nested = $obj->can_be_nested;
|
||||
$type->allowed_parent_types = $obj->allowed_parent_types;
|
||||
$type->picto = $obj->picto;
|
||||
$type->is_system = $obj->is_system;
|
||||
$type->position = $obj->position;
|
||||
$type->active = $obj->active;
|
||||
$type->system_label = $obj->system_label;
|
||||
$type->system_code = $obj->system_code;
|
||||
|
||||
$results[] = $type;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch fields for this type
|
||||
*
|
||||
* @param int $activeOnly Only active fields
|
||||
* @return array Array of field objects
|
||||
*/
|
||||
public function fetchFields($activeOnly = 1)
|
||||
{
|
||||
$results = array();
|
||||
|
||||
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field";
|
||||
$sql .= " WHERE fk_anlage_type = ".((int) $this->id);
|
||||
if ($activeOnly) {
|
||||
$sql .= " AND active = 1";
|
||||
}
|
||||
$sql .= " ORDER BY position ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$results[] = $obj;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
}
|
||||
|
||||
$this->fields = $results;
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed parent types as array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedParentTypesArray()
|
||||
{
|
||||
if (empty($this->allowed_parent_types)) {
|
||||
return array();
|
||||
}
|
||||
return array_map('trim', explode(',', $this->allowed_parent_types));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this type can be child of another type
|
||||
*
|
||||
* @param string $parentTypeRef Parent type reference
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeChildOf($parentTypeRef)
|
||||
{
|
||||
if (empty($this->allowed_parent_types)) {
|
||||
return true; // No restriction = can be anywhere
|
||||
}
|
||||
$allowed = $this->getAllowedParentTypesArray();
|
||||
return in_array($parentTypeRef, $allowed);
|
||||
}
|
||||
}
|
||||
802
class/favoriteproduct.class.php
Executable file
802
class/favoriteproduct.class.php
Executable file
|
|
@ -0,0 +1,802 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class FavoriteProduct
|
||||
* Manages favorite products for customers
|
||||
*/
|
||||
class FavoriteProduct extends CommonObject
|
||||
{
|
||||
public $element = 'favoriteproduct';
|
||||
public $table_element = 'kundenkarte_favorite_products';
|
||||
|
||||
public $fk_soc;
|
||||
public $fk_contact;
|
||||
public $fk_product;
|
||||
public $qty;
|
||||
public $rang;
|
||||
public $note;
|
||||
public $active;
|
||||
public $date_creation;
|
||||
public $fk_user_creat;
|
||||
public $fk_user_modif;
|
||||
|
||||
// Loaded objects
|
||||
public $product;
|
||||
public $societe;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DoliDB $db Database handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create object in database
|
||||
*
|
||||
* @param User $user User that creates
|
||||
* @param bool $notrigger false=launch triggers, true=disable triggers
|
||||
* @return int Return integer <0 if KO, Id of created object if OK
|
||||
*/
|
||||
public function create($user, $notrigger = false)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$error = 0;
|
||||
$now = dol_now();
|
||||
|
||||
// Check parameters
|
||||
if (empty($this->fk_soc) || empty($this->fk_product)) {
|
||||
$this->error = 'ErrorMissingParameters';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if already exists
|
||||
if ($this->alreadyExists($this->fk_soc, $this->fk_product, $this->fk_contact)) {
|
||||
$this->error = 'ErrorRecordAlreadyExists';
|
||||
return -2;
|
||||
}
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
// Get max rang for this customer/contact to add at end of list
|
||||
$maxRang = 0;
|
||||
$sqlRang = "SELECT MAX(rang) as maxrang FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sqlRang .= " WHERE fk_soc = ".((int) $this->fk_soc);
|
||||
if ($this->fk_contact > 0) {
|
||||
$sqlRang .= " AND fk_contact = ".((int) $this->fk_contact);
|
||||
} else {
|
||||
$sqlRang .= " AND (fk_contact IS NULL OR fk_contact = 0)";
|
||||
}
|
||||
$sqlRang .= " AND entity = ".((int) $conf->entity);
|
||||
$resRang = $this->db->query($sqlRang);
|
||||
if ($resRang) {
|
||||
$objRang = $this->db->fetch_object($resRang);
|
||||
$maxRang = ($objRang->maxrang !== null) ? ((int) $objRang->maxrang + 1) : 0;
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||
$sql .= "entity, fk_soc, fk_contact, fk_product, qty, rang, note, active, date_creation, fk_user_creat";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= ((int) $conf->entity);
|
||||
$sql .= ", ".((int) $this->fk_soc);
|
||||
$sql .= ", ".($this->fk_contact > 0 ? ((int) $this->fk_contact) : "NULL");
|
||||
$sql .= ", ".((int) $this->fk_product);
|
||||
$sql .= ", ".((float) ($this->qty > 0 ? $this->qty : 1));
|
||||
$sql .= ", ".((int) $maxRang);
|
||||
$sql .= ", ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL");
|
||||
$sql .= ", ".((int) ($this->active !== null ? $this->active : 1));
|
||||
$sql .= ", '".$this->db->idate($now)."'";
|
||||
$sql .= ", ".((int) $user->id);
|
||||
$sql .= ")";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
||||
$this->date_creation = $now;
|
||||
$this->fk_user_creat = $user->id;
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load object from database
|
||||
*
|
||||
* @param int $id ID of record
|
||||
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$sql = "SELECT rowid, entity, fk_soc, fk_contact, fk_product, qty, rang, note, active,";
|
||||
$sql .= " date_creation, tms, fk_user_creat, fk_user_modif";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sql .= " WHERE rowid = ".((int) $id);
|
||||
$sql .= " AND entity = ".((int) $conf->entity);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
if ($this->db->num_rows($resql)) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
|
||||
$this->id = $obj->rowid;
|
||||
$this->entity = $obj->entity;
|
||||
$this->fk_soc = $obj->fk_soc;
|
||||
$this->fk_contact = $obj->fk_contact;
|
||||
$this->fk_product = $obj->fk_product;
|
||||
$this->qty = $obj->qty;
|
||||
$this->rang = $obj->rang;
|
||||
$this->note = $obj->note;
|
||||
$this->active = $obj->active;
|
||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||
$this->tms = $this->db->jdate($obj->tms);
|
||||
$this->fk_user_creat = $obj->fk_user_creat;
|
||||
$this->fk_user_modif = $obj->fk_user_modif;
|
||||
|
||||
$this->db->free($resql);
|
||||
return 1;
|
||||
} else {
|
||||
$this->db->free($resql);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update object in database
|
||||
*
|
||||
* @param User $user User that modifies
|
||||
* @param bool $notrigger false=launch triggers, true=disable triggers
|
||||
* @return int Return integer <0 if KO, >0 if OK
|
||||
*/
|
||||
public function update($user, $notrigger = false)
|
||||
{
|
||||
$error = 0;
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||
$sql .= " qty = ".((float) $this->qty);
|
||||
$sql .= ", rang = ".((int) $this->rang);
|
||||
$sql .= ", note = ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL");
|
||||
$sql .= ", active = ".((int) $this->active);
|
||||
$sql .= ", fk_user_modif = ".((int) $user->id);
|
||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete object in database
|
||||
*
|
||||
* @param User $user User that deletes
|
||||
* @param bool $notrigger false=launch triggers, true=disable triggers
|
||||
* @return int Return integer <0 if KO, >0 if OK
|
||||
*/
|
||||
public function delete($user, $notrigger = false)
|
||||
{
|
||||
$error = 0;
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$error++;
|
||||
$this->errors[] = "Error ".$this->db->lasterror();
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->db->rollback();
|
||||
return -1 * $error;
|
||||
} else {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a product is already a favorite for a customer/contact
|
||||
*
|
||||
* @param int $socid Customer ID
|
||||
* @param int $productid Product ID
|
||||
* @param int $contactid Contact ID (optional)
|
||||
* @return bool
|
||||
*/
|
||||
public function alreadyExists($socid, $productid, $contactid = 0)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sql .= " WHERE fk_soc = ".((int) $socid);
|
||||
$sql .= " AND fk_product = ".((int) $productid);
|
||||
if ($contactid > 0) {
|
||||
$sql .= " AND fk_contact = ".((int) $contactid);
|
||||
} else {
|
||||
$sql .= " AND (fk_contact IS NULL OR fk_contact = 0)";
|
||||
}
|
||||
$sql .= " AND entity = ".((int) $conf->entity);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
return ($this->db->num_rows($resql) > 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all favorite products for a customer
|
||||
*
|
||||
* @param int $socid Customer ID
|
||||
* @param int $activeonly Only active favorites
|
||||
* @return array|int Array of FavoriteProduct objects or -1 if error
|
||||
*/
|
||||
public function fetchAllBySociete($socid, $activeonly = 1)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$results = array();
|
||||
|
||||
$sql = "SELECT fp.rowid, fp.fk_soc, fp.fk_product, fp.qty, fp.rang, fp.note, fp.active,";
|
||||
$sql .= " p.ref as product_ref, p.label as product_label, p.price, p.price_ttc, p.tva_tx";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as fp";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON fp.fk_product = p.rowid";
|
||||
$sql .= " WHERE fp.fk_soc = ".((int) $socid);
|
||||
// Only thirdparty-level favorites, not contact-specific
|
||||
$sql .= " AND (fp.fk_contact IS NULL OR fp.fk_contact = 0)";
|
||||
$sql .= " AND fp.entity = ".((int) $conf->entity);
|
||||
if ($activeonly) {
|
||||
$sql .= " AND fp.active = 1";
|
||||
}
|
||||
$sql .= " ORDER BY fp.rang ASC, p.label ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$fav = new FavoriteProduct($this->db);
|
||||
$fav->id = $obj->rowid;
|
||||
$fav->fk_soc = $obj->fk_soc;
|
||||
$fav->fk_product = $obj->fk_product;
|
||||
$fav->qty = $obj->qty;
|
||||
$fav->rang = $obj->rang;
|
||||
$fav->note = $obj->note;
|
||||
$fav->active = $obj->active;
|
||||
|
||||
// Product info
|
||||
$fav->product_ref = $obj->product_ref;
|
||||
$fav->product_label = $obj->product_label;
|
||||
$fav->product_price = $obj->price;
|
||||
$fav->product_price_ttc = $obj->price_ttc;
|
||||
$fav->product_tva_tx = $obj->tva_tx;
|
||||
|
||||
$results[] = $fav;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
return $results;
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a favorite product up in the list
|
||||
*
|
||||
* @param int $id Favorite ID to move
|
||||
* @param int $socid Customer ID
|
||||
* @return int 1 if OK, <0 if KO
|
||||
*/
|
||||
public function moveUp($id, $socid)
|
||||
{
|
||||
return $this->movePosition($id, $socid, 'up');
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a favorite product down in the list
|
||||
*
|
||||
* @param int $id Favorite ID to move
|
||||
* @param int $socid Customer ID
|
||||
* @return int 1 if OK, <0 if KO
|
||||
*/
|
||||
public function moveDown($id, $socid)
|
||||
{
|
||||
return $this->movePosition($id, $socid, 'down');
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a favorite product position
|
||||
*
|
||||
* @param int $id Favorite ID to move
|
||||
* @param int $socid Customer ID
|
||||
* @param string $direction 'up' or 'down'
|
||||
* @return int 1 if OK, <0 if KO
|
||||
*/
|
||||
private function movePosition($id, $socid, $direction)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
// Get all favorites ordered by rang
|
||||
$sql = "SELECT rowid, rang FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sql .= " WHERE fk_soc = ".((int) $socid);
|
||||
$sql .= " AND entity = ".((int) $conf->entity);
|
||||
$sql .= " ORDER BY rang ASC, rowid ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
|
||||
$items = array();
|
||||
$currentIndex = -1;
|
||||
$i = 0;
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$items[$i] = array('id' => $obj->rowid, 'rang' => $i);
|
||||
if ($obj->rowid == $id) {
|
||||
$currentIndex = $i;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
|
||||
if ($currentIndex < 0) {
|
||||
return 0; // Item not found
|
||||
}
|
||||
|
||||
// Calculate new positions
|
||||
$swapIndex = -1;
|
||||
if ($direction == 'up' && $currentIndex > 0) {
|
||||
$swapIndex = $currentIndex - 1;
|
||||
} elseif ($direction == 'down' && $currentIndex < count($items) - 1) {
|
||||
$swapIndex = $currentIndex + 1;
|
||||
}
|
||||
|
||||
if ($swapIndex < 0) {
|
||||
return 0; // Cannot move
|
||||
}
|
||||
|
||||
// Swap positions
|
||||
$this->db->begin();
|
||||
|
||||
$sql1 = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET rang = ".((int) $swapIndex);
|
||||
$sql1 .= " WHERE rowid = ".((int) $items[$currentIndex]['id']);
|
||||
|
||||
$sql2 = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET rang = ".((int) $currentIndex);
|
||||
$sql2 .= " WHERE rowid = ".((int) $items[$swapIndex]['id']);
|
||||
|
||||
if ($this->db->query($sql1) && $this->db->query($sql2)) {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an order from selected favorite products
|
||||
*
|
||||
* @param User $user User creating the order
|
||||
* @param int $socid Customer ID
|
||||
* @param array $selectedIds Array of favorite product IDs to include
|
||||
* @param array $quantities Optional array of quantities (id => qty)
|
||||
* @return int Order ID if OK, <0 if KO
|
||||
*/
|
||||
public function generateOrder($user, $socid, $selectedIds, $quantities = array())
|
||||
{
|
||||
global $conf, $langs, $mysoc;
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||
|
||||
if (empty($selectedIds)) {
|
||||
$this->error = 'NoProductsSelected';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Load thirdparty (required for VAT calculation)
|
||||
$societe = new Societe($this->db);
|
||||
if ($societe->fetch($socid) <= 0) {
|
||||
$this->error = 'ErrorLoadingThirdparty';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Fetch selected favorites
|
||||
$favorites = $this->fetchAllBySociete($socid);
|
||||
if (!is_array($favorites)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Filter to selected only
|
||||
$toAdd = array();
|
||||
foreach ($favorites as $fav) {
|
||||
if (in_array($fav->id, $selectedIds)) {
|
||||
$qty = isset($quantities[$fav->id]) ? (float) $quantities[$fav->id] : $fav->qty;
|
||||
if ($qty > 0) {
|
||||
$toAdd[] = array(
|
||||
'product_id' => $fav->fk_product,
|
||||
'qty' => $qty
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($toAdd)) {
|
||||
$this->error = 'NoValidProductsToAdd';
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Create order
|
||||
$order = new Commande($this->db);
|
||||
$order->socid = $socid;
|
||||
$order->thirdparty = $societe; // Required for VAT calculation
|
||||
$order->date = dol_now();
|
||||
$order->ref_client = $societe->name.' - '.$langs->trans('FavoriteProducts'); // Ihr Zeichen
|
||||
$order->note_private = $langs->trans('OrderGeneratedFromFavorites');
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
// First create the order header
|
||||
$result = $order->create($user);
|
||||
|
||||
if ($result <= 0) {
|
||||
$this->error = $order->error;
|
||||
$this->errors = $order->errors;
|
||||
$this->db->rollback();
|
||||
return -3;
|
||||
}
|
||||
|
||||
// Now add products to the created order
|
||||
foreach ($toAdd as $item) {
|
||||
$product = new Product($this->db);
|
||||
$product->fetch($item['product_id']);
|
||||
|
||||
// Get VAT rate for this product and customer (mysoc = seller, societe = buyer)
|
||||
$tva_tx = get_default_tva($mysoc, $societe, $product->id);
|
||||
$localtax1_tx = get_default_localtax($mysoc, $societe, 1, $product->id);
|
||||
$localtax2_tx = get_default_localtax($mysoc, $societe, 2, $product->id);
|
||||
|
||||
$lineResult = $order->addline(
|
||||
$product->label, // Description
|
||||
$product->price, // Unit price HT
|
||||
$item['qty'], // Quantity
|
||||
$tva_tx, // VAT rate
|
||||
$localtax1_tx, // Local tax 1
|
||||
$localtax2_tx, // Local tax 2
|
||||
$product->id, // Product ID
|
||||
0, // Discount
|
||||
0, // Info bits
|
||||
0, // fk_remise_except
|
||||
'HT', // Price base type
|
||||
0, // Unit price TTC
|
||||
'', // Date start
|
||||
'', // Date end
|
||||
0, // Type (0=product)
|
||||
-1, // Rang
|
||||
0, // Special code
|
||||
0, // fk_parent_line
|
||||
0, // fk_fournprice
|
||||
0, // pa_ht
|
||||
$product->label, // Label
|
||||
array(), // Array options
|
||||
$product->fk_unit // Unit
|
||||
);
|
||||
|
||||
if ($lineResult < 0) {
|
||||
$this->error = $order->error;
|
||||
$this->errors = $order->errors;
|
||||
$this->db->rollback();
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->commit();
|
||||
return $order->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all favorite products for a contact/address
|
||||
*
|
||||
* @param int $contactid Contact ID
|
||||
* @param int $activeonly Only active favorites
|
||||
* @return array|int Array of FavoriteProduct objects or -1 if error
|
||||
*/
|
||||
public function fetchAllByContact($contactid, $activeonly = 1)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$results = array();
|
||||
|
||||
$sql = "SELECT fp.rowid, fp.fk_soc, fp.fk_contact, fp.fk_product, fp.qty, fp.rang, fp.note, fp.active,";
|
||||
$sql .= " p.ref as product_ref, p.label as product_label, p.price, p.price_ttc, p.tva_tx";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as fp";
|
||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON fp.fk_product = p.rowid";
|
||||
$sql .= " WHERE fp.fk_contact = ".((int) $contactid);
|
||||
$sql .= " AND fp.entity = ".((int) $conf->entity);
|
||||
if ($activeonly) {
|
||||
$sql .= " AND fp.active = 1";
|
||||
}
|
||||
$sql .= " ORDER BY fp.rang ASC, p.label ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$fav = new FavoriteProduct($this->db);
|
||||
$fav->id = $obj->rowid;
|
||||
$fav->fk_soc = $obj->fk_soc;
|
||||
$fav->fk_contact = $obj->fk_contact;
|
||||
$fav->fk_product = $obj->fk_product;
|
||||
$fav->qty = $obj->qty;
|
||||
$fav->rang = $obj->rang;
|
||||
$fav->note = $obj->note;
|
||||
$fav->active = $obj->active;
|
||||
|
||||
// Product info
|
||||
$fav->product_ref = $obj->product_ref;
|
||||
$fav->product_label = $obj->product_label;
|
||||
$fav->product_price = $obj->price;
|
||||
$fav->product_price_ttc = $obj->price_ttc;
|
||||
$fav->product_tva_tx = $obj->tva_tx;
|
||||
|
||||
$results[] = $fav;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
return $results;
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a favorite product up in the list (for contact)
|
||||
*
|
||||
* @param int $id Favorite ID to move
|
||||
* @param int $contactid Contact ID
|
||||
* @return int 1 if OK, <0 if KO
|
||||
*/
|
||||
public function moveUpByContact($id, $contactid)
|
||||
{
|
||||
return $this->movePositionByContact($id, $contactid, 'up');
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a favorite product down in the list (for contact)
|
||||
*
|
||||
* @param int $id Favorite ID to move
|
||||
* @param int $contactid Contact ID
|
||||
* @return int 1 if OK, <0 if KO
|
||||
*/
|
||||
public function moveDownByContact($id, $contactid)
|
||||
{
|
||||
return $this->movePositionByContact($id, $contactid, 'down');
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a favorite product position (for contact)
|
||||
*
|
||||
* @param int $id Favorite ID to move
|
||||
* @param int $contactid Contact ID
|
||||
* @param string $direction 'up' or 'down'
|
||||
* @return int 1 if OK, <0 if KO
|
||||
*/
|
||||
private function movePositionByContact($id, $contactid, $direction)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$sql = "SELECT rowid, rang FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||
$sql .= " WHERE fk_contact = ".((int) $contactid);
|
||||
$sql .= " AND entity = ".((int) $conf->entity);
|
||||
$sql .= " ORDER BY rang ASC, rowid ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
|
||||
$items = array();
|
||||
$currentIndex = -1;
|
||||
$i = 0;
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$items[$i] = array('id' => $obj->rowid, 'rang' => $i);
|
||||
if ($obj->rowid == $id) {
|
||||
$currentIndex = $i;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
|
||||
if ($currentIndex < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$swapIndex = -1;
|
||||
if ($direction == 'up' && $currentIndex > 0) {
|
||||
$swapIndex = $currentIndex - 1;
|
||||
} elseif ($direction == 'down' && $currentIndex < count($items) - 1) {
|
||||
$swapIndex = $currentIndex + 1;
|
||||
}
|
||||
|
||||
if ($swapIndex < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$sql1 = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET rang = ".((int) $swapIndex);
|
||||
$sql1 .= " WHERE rowid = ".((int) $items[$currentIndex]['id']);
|
||||
|
||||
$sql2 = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET rang = ".((int) $currentIndex);
|
||||
$sql2 .= " WHERE rowid = ".((int) $items[$swapIndex]['id']);
|
||||
|
||||
if ($this->db->query($sql1) && $this->db->query($sql2)) {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an order from selected favorite products (for contact)
|
||||
*
|
||||
* @param User $user User creating the order
|
||||
* @param int $socid Customer ID
|
||||
* @param int $contactid Contact ID
|
||||
* @param array $selectedIds Array of favorite product IDs to include
|
||||
* @param array $quantities Optional array of quantities (id => qty)
|
||||
* @return int Order ID if OK, <0 if KO
|
||||
*/
|
||||
public function generateOrderByContact($user, $socid, $contactid, $selectedIds, $quantities = array())
|
||||
{
|
||||
global $conf, $langs, $mysoc;
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||
|
||||
if (empty($selectedIds)) {
|
||||
$this->error = 'NoProductsSelected';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Load thirdparty
|
||||
$societe = new Societe($this->db);
|
||||
if ($societe->fetch($socid) <= 0) {
|
||||
$this->error = 'ErrorLoadingThirdparty';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Load contact
|
||||
$contact = new Contact($this->db);
|
||||
if ($contact->fetch($contactid) <= 0) {
|
||||
$this->error = 'ErrorLoadingContact';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Fetch selected favorites
|
||||
$favorites = $this->fetchAllByContact($contactid);
|
||||
if (!is_array($favorites)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Filter to selected only
|
||||
$toAdd = array();
|
||||
foreach ($favorites as $fav) {
|
||||
if (in_array($fav->id, $selectedIds)) {
|
||||
$qty = isset($quantities[$fav->id]) ? (float) $quantities[$fav->id] : $fav->qty;
|
||||
if ($qty > 0) {
|
||||
$toAdd[] = array(
|
||||
'product_id' => $fav->fk_product,
|
||||
'qty' => $qty
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($toAdd)) {
|
||||
$this->error = 'NoValidProductsToAdd';
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Create order
|
||||
$order = new Commande($this->db);
|
||||
$order->socid = $socid;
|
||||
$order->thirdparty = $societe;
|
||||
$order->date = dol_now();
|
||||
// Ihr Zeichen: Kunde - Kontakt/Adresse - Favoriten
|
||||
$order->ref_client = $societe->name.' - '.$contact->getFullName($langs).' - '.$langs->trans('FavoriteProducts');
|
||||
$order->note_private = $langs->trans('OrderGeneratedFromFavorites');
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$result = $order->create($user);
|
||||
|
||||
if ($result <= 0) {
|
||||
$this->error = $order->error;
|
||||
$this->errors = $order->errors;
|
||||
$this->db->rollback();
|
||||
return -3;
|
||||
}
|
||||
|
||||
// Add products
|
||||
foreach ($toAdd as $item) {
|
||||
$product = new Product($this->db);
|
||||
$product->fetch($item['product_id']);
|
||||
|
||||
$tva_tx = get_default_tva($mysoc, $societe, $product->id);
|
||||
$localtax1_tx = get_default_localtax($mysoc, $societe, 1, $product->id);
|
||||
$localtax2_tx = get_default_localtax($mysoc, $societe, 2, $product->id);
|
||||
|
||||
$lineResult = $order->addline(
|
||||
$product->label,
|
||||
$product->price,
|
||||
$item['qty'],
|
||||
$tva_tx,
|
||||
$localtax1_tx,
|
||||
$localtax2_tx,
|
||||
$product->id,
|
||||
0, 0, 0, 'HT', 0, '', '', 0, -1, 0, 0, 0, 0,
|
||||
$product->label,
|
||||
array(),
|
||||
$product->fk_unit
|
||||
);
|
||||
|
||||
if ($lineResult < 0) {
|
||||
$this->error = $order->error;
|
||||
$this->errors = $order->errors;
|
||||
$this->db->rollback();
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->commit();
|
||||
return $order->id;
|
||||
}
|
||||
}
|
||||
548
core/modules/modKundenKarte.class.php
Executable file
548
core/modules/modKundenKarte.class.php
Executable file
|
|
@ -0,0 +1,548 @@
|
|||
<?php
|
||||
/* Copyright (C) 2004-2018 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||
* Copyright (C) 2018-2019 Nicolas ZABOURI <info@inovea-conseil.com>
|
||||
* Copyright (C) 2019-2024 Frédéric France <frederic.france@free.fr>
|
||||
* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \defgroup kundenkarte Module KundenKarte
|
||||
* \brief KundenKarte module descriptor.
|
||||
*
|
||||
* \file htdocs/kundenkarte/core/modules/modKundenKarte.class.php
|
||||
* \ingroup kundenkarte
|
||||
* \brief Description and activation file for module KundenKarte
|
||||
*/
|
||||
include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
|
||||
|
||||
|
||||
/**
|
||||
* Description and activation class for module KundenKarte
|
||||
*/
|
||||
class modKundenKarte extends DolibarrModules
|
||||
{
|
||||
/**
|
||||
* Constructor. Define names, constants, directories, boxes, permissions
|
||||
*
|
||||
* @param DoliDB $db Database handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
global $conf, $langs;
|
||||
|
||||
$this->db = $db;
|
||||
|
||||
// Id for module (must be unique).
|
||||
// Use here a free id (See in Home -> System information -> Dolibarr for list of used modules id).
|
||||
$this->numero = 500015; // TODO Go on page https://wiki.dolibarr.org/index.php/List_of_modules_id to reserve an id number for your module
|
||||
|
||||
// Key text used to identify module (for permissions, menus, etc...)
|
||||
$this->rights_class = 'kundenkarte';
|
||||
|
||||
// Family can be 'base' (core modules),'crm','financial','hr','projects','products','ecm','technic' (transverse modules),'interface' (link with external tools),'other','...'
|
||||
// It is used to group modules by family in module setup page
|
||||
$this->family = "other";
|
||||
|
||||
// Module position in the family on 2 digits ('01', '10', '20', ...)
|
||||
$this->module_position = '90';
|
||||
|
||||
// Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this)
|
||||
//$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily")));
|
||||
// Module label (no space allowed), used if translation string 'ModuleKundenKarteName' not found (KundenKarte is name of module).
|
||||
$this->name = preg_replace('/^mod/i', '', get_class($this));
|
||||
|
||||
// DESCRIPTION_FLAG
|
||||
// Module description, used if translation string 'ModuleKundenKarteDesc' not found (KundenKarte is name of module).
|
||||
$this->description = "KundenKarteDescription";
|
||||
// Used only if file README.md and README-LL.md not found.
|
||||
$this->descriptionlong = "KundenKarteDescription";
|
||||
|
||||
// Author
|
||||
$this->editor_name = 'Alles Watt läuft (Testsystem)';
|
||||
$this->editor_url = ''; // Must be an external online web site
|
||||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte'
|
||||
|
||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||
$this->version = '1.1';
|
||||
// Url to the file with your last numberversion of this module
|
||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||
|
||||
// Key used in llx_const table to save module status enabled/disabled (where KUNDENKARTE is value of property name of module in uppercase)
|
||||
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
|
||||
|
||||
// Name of image file used for this module.
|
||||
// If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue'
|
||||
// If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module'
|
||||
// To use a supported fa-xxx css style of font awesome, use this->picto='xxx'
|
||||
$this->picto = 'fa-id-card';
|
||||
|
||||
// Define some features supported by module (triggers, login, substitutions, menus, css, etc...)
|
||||
$this->module_parts = array(
|
||||
// Set this to 1 if module has its own trigger directory (core/triggers)
|
||||
'triggers' => 0,
|
||||
// Set this to 1 if module has its own login method file (core/login)
|
||||
'login' => 0,
|
||||
// Set this to 1 if module has its own substitution function file (core/substitutions)
|
||||
'substitutions' => 0,
|
||||
// Set this to 1 if module has its own menus handler directory (core/menus)
|
||||
'menus' => 0,
|
||||
// Set this to 1 if module overwrite template dir (core/tpl)
|
||||
'tpl' => 0,
|
||||
// Set this to 1 if module has its own barcode directory (core/modules/barcode)
|
||||
'barcode' => 0,
|
||||
// Set this to 1 if module has its own models directory (core/modules/xxx)
|
||||
'models' => 0,
|
||||
// Set this to 1 if module has its own printing directory (core/modules/printing)
|
||||
'printing' => 0,
|
||||
// Set this to 1 if module has its own theme directory (theme)
|
||||
'theme' => 0,
|
||||
// Set this to relative path of css file if module has its own css file
|
||||
'css' => array(
|
||||
'/kundenkarte/css/kundenkarte.css',
|
||||
),
|
||||
// Set this to relative path of js file if module must load a js on all pages
|
||||
'js' => array(
|
||||
'/kundenkarte/js/kundenkarte.js',
|
||||
),
|
||||
// Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all'
|
||||
/* BEGIN MODULEBUILDER HOOKSCONTEXTS */
|
||||
'hooks' => array(
|
||||
// 'data' => array(
|
||||
// 'hookcontext1',
|
||||
// 'hookcontext2',
|
||||
// ),
|
||||
// 'entity' => '0',
|
||||
),
|
||||
/* END MODULEBUILDER HOOKSCONTEXTS */
|
||||
// Set this to 1 if features of module are opened to external users
|
||||
'moduleforexternal' => 0,
|
||||
// Set this to 1 if the module provides a website template into doctemplates/websites/website_template-mytemplate
|
||||
'websitetemplates' => 0,
|
||||
// Set this to 1 if the module provides a captcha driver
|
||||
'captcha' => 0
|
||||
);
|
||||
|
||||
// Data directories to create when module is enabled.
|
||||
$this->dirs = array(
|
||||
"/kundenkarte/temp",
|
||||
"/kundenkarte/anlagen"
|
||||
);
|
||||
|
||||
// Config pages. Put here list of php page, stored into kundenkarte/admin directory, to use to setup module.
|
||||
$this->config_page_url = array("setup.php@kundenkarte");
|
||||
|
||||
// Dependencies
|
||||
// A condition to hide module
|
||||
$this->hidden = getDolGlobalInt('MODULE_KUNDENKARTE_DISABLED'); // A condition to disable module;
|
||||
// List of module class names that must be enabled if this module is enabled. Example: array('always'=>array('modModuleToEnable1','modModuleToEnable2'), 'FR'=>array('modModuleToEnableFR')...)
|
||||
$this->depends = array();
|
||||
// List of module class names to disable if this one is disabled. Example: array('modModuleToDisable1', ...)
|
||||
$this->requiredby = array();
|
||||
// List of module class names this module is in conflict with. Example: array('modModuleToDisable1', ...)
|
||||
$this->conflictwith = array();
|
||||
|
||||
// The language file dedicated to your module
|
||||
$this->langfiles = array("kundenkarte@kundenkarte");
|
||||
|
||||
// Prerequisites
|
||||
$this->phpmin = array(7, 1); // Minimum version of PHP required by module
|
||||
// $this->phpmax = array(8, 0); // Maximum version of PHP required by module
|
||||
$this->need_dolibarr_version = array(19, -3); // Minimum version of Dolibarr required by module
|
||||
// $this->max_dolibarr_version = array(19, -3); // Maximum version of Dolibarr required by module
|
||||
$this->need_javascript_ajax = 0;
|
||||
|
||||
// Messages at activation
|
||||
$this->warnings_activation = array(); // Warning to show when we activate module. array('always'='text') or array('FR'='textfr','MX'='textmx'...)
|
||||
$this->warnings_activation_ext = array(); // Warning to show when we activate an external module. array('always'='text') or array('FR'='textfr','MX'='textmx'...)
|
||||
//$this->automatic_activation = array('FR'=>'KundenKarteWasAutomaticallyActivatedBecauseOfYourCountryChoice');
|
||||
//$this->always_enabled = true; // If true, can't be disabled
|
||||
|
||||
// Constants
|
||||
// List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive)
|
||||
// Example: $this->const=array(1 => array('KUNDENKARTE_MYNEWCONST1', 'chaine', 'myvalue', 'This is a constant to add', 1),
|
||||
// 2 => array('KUNDENKARTE_MYNEWCONST2', 'chaine', 'myvalue', 'This is another constant to add', 0, 'current', 1)
|
||||
// );
|
||||
$this->const = array();
|
||||
|
||||
// Some keys to add into the overwriting translation tables
|
||||
/*$this->overwrite_translation = array(
|
||||
'en_US:ParentCompany'=>'Parent company or reseller',
|
||||
'fr_FR:ParentCompany'=>'Maison mère ou revendeur'
|
||||
)*/
|
||||
|
||||
if (!isModEnabled("kundenkarte")) {
|
||||
$conf->kundenkarte = new stdClass();
|
||||
$conf->kundenkarte->enabled = 0;
|
||||
}
|
||||
|
||||
// Array to add new pages in new tabs
|
||||
/* BEGIN MODULEBUILDER TABS */
|
||||
$this->tabs = array(
|
||||
// Add tab for favorite products on thirdparty card
|
||||
array('data' => 'thirdparty:+favoriteproducts:FavoriteProducts:kundenkarte@kundenkarte:$user->hasRight(\'kundenkarte\', \'read\'):/kundenkarte/tabs/favoriteproducts.php?id=__ID__'),
|
||||
// Add tab for technical installations on thirdparty card
|
||||
array('data' => 'thirdparty:+anlagen:TechnicalInstallations:kundenkarte@kundenkarte:$user->hasRight(\'kundenkarte\', \'read\'):/kundenkarte/tabs/anlagen.php?id=__ID__'),
|
||||
// Add tab for favorite products on contact card (for addresses/buildings)
|
||||
array('data' => 'contact:+favoriteproducts:FavoriteProducts:kundenkarte@kundenkarte:$user->hasRight(\'kundenkarte\', \'read\'):/kundenkarte/tabs/contact_favoriteproducts.php?id=__ID__'),
|
||||
// Add tab for technical installations on contact card (for addresses/buildings)
|
||||
array('data' => 'contact:+anlagen:TechnicalInstallations:kundenkarte@kundenkarte:$user->hasRight(\'kundenkarte\', \'read\'):/kundenkarte/tabs/contact_anlagen.php?id=__ID__'),
|
||||
);
|
||||
/* END MODULEBUILDER TABS */
|
||||
// Example:
|
||||
// To add a new tab identified by code tabname1
|
||||
// $this->tabs[] = array('data' => 'objecttype:+tabname1:Title1:mylangfile@kundenkarte:$user->hasRight(\'kundenkarte\', \'read\'):/kundenkarte/mynewtab1.php?id=__ID__');
|
||||
// To add another new tab identified by code tabname2. Label will be result of calling all substitution functions on 'Title2' key.
|
||||
// $this->tabs[] = array('data' => 'objecttype:+tabname2:SUBSTITUTION_Title2:mylangfile@kundenkarte:$user->hasRight(\'othermodule\', \'read\'):/kundenkarte/mynewtab2.php?id=__ID__',
|
||||
// To remove an existing tab identified by code tabname
|
||||
// $this->tabs[] = array('data' => 'objecttype:-tabname:NU:conditiontoremove');
|
||||
//
|
||||
// Where objecttype can be
|
||||
// 'categories_x' to add a tab in category view (replace 'x' by type of category (0=product, 1=supplier, 2=customer, 3=member)
|
||||
// 'contact' to add a tab in contact view
|
||||
// 'contract' to add a tab in contract view
|
||||
// 'delivery' to add a tab in delivery view
|
||||
// 'group' to add a tab in group view
|
||||
// 'intervention' to add a tab in intervention view
|
||||
// 'invoice' to add a tab in customer invoice view
|
||||
// 'supplier_invoice' to add a tab in supplier invoice view
|
||||
// 'member' to add a tab in foundation member view
|
||||
// 'opensurveypoll' to add a tab in opensurvey poll view
|
||||
// 'order' to add a tab in sale order view
|
||||
// 'supplier_order' to add a tab in supplier order view
|
||||
// 'payment' to add a tab in payment view
|
||||
// 'supplier_payment' to add a tab in supplier payment view
|
||||
// 'product' to add a tab in product view
|
||||
// 'propal' to add a tab in propal view
|
||||
// 'project' to add a tab in project view
|
||||
// 'stock' to add a tab in stock view
|
||||
// 'thirdparty' to add a tab in third party view
|
||||
// 'user' to add a tab in user view
|
||||
|
||||
|
||||
// Dictionaries
|
||||
/* Example:
|
||||
$this->dictionaries=array(
|
||||
'langs' => 'kundenkarte@kundenkarte',
|
||||
// List of tables we want to see into dictionary editor
|
||||
'tabname' => array("table1", "table2", "table3"),
|
||||
// Label of tables
|
||||
'tablib' => array("Table1", "Table2", "Table3"),
|
||||
// Request to select fields
|
||||
'tabsql' => array('SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table1 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table2 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.$this->db->prefix().'table3 as f'),
|
||||
// Sort order
|
||||
'tabsqlsort' => array("label ASC", "label ASC", "label ASC"),
|
||||
// List of fields (result of select to show dictionary)
|
||||
'tabfield' => array("code,label", "code,label", "code,label"),
|
||||
// List of fields (list of fields to edit a record)
|
||||
'tabfieldvalue' => array("code,label", "code,label", "code,label"),
|
||||
// List of fields (list of fields for insert)
|
||||
'tabfieldinsert' => array("code,label", "code,label", "code,label"),
|
||||
// Name of columns with primary key (try to always name it 'rowid')
|
||||
'tabrowid' => array("rowid", "rowid", "rowid"),
|
||||
// Condition to show each dictionary
|
||||
'tabcond' => array(isModEnabled('kundenkarte'), isModEnabled('kundenkarte'), isModEnabled('kundenkarte')),
|
||||
// Tooltip for every fields of dictionaries: DO NOT PUT AN EMPTY ARRAY
|
||||
'tabhelp' => array(array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), ...),
|
||||
);
|
||||
*/
|
||||
/* BEGIN MODULEBUILDER DICTIONARIES */
|
||||
$this->dictionaries = array();
|
||||
/* END MODULEBUILDER DICTIONARIES */
|
||||
|
||||
// Boxes/Widgets
|
||||
// Add here list of php file(s) stored in kundenkarte/core/boxes that contains a class to show a widget.
|
||||
/* BEGIN MODULEBUILDER WIDGETS */
|
||||
$this->boxes = array(
|
||||
// 0 => array(
|
||||
// 'file' => 'kundenkartewidget1.php@kundenkarte',
|
||||
// 'note' => 'Widget provided by KundenKarte',
|
||||
// 'enabledbydefaulton' => 'Home',
|
||||
// ),
|
||||
// ...
|
||||
);
|
||||
/* END MODULEBUILDER WIDGETS */
|
||||
|
||||
// Cronjobs (List of cron jobs entries to add when module is enabled)
|
||||
// unit_frequency must be 60 for minute, 3600 for hour, 86400 for day, 604800 for week
|
||||
/* BEGIN MODULEBUILDER CRON */
|
||||
$this->cronjobs = array(
|
||||
// 0 => array(
|
||||
// 'label' => 'MyJob label',
|
||||
// 'jobtype' => 'method',
|
||||
// 'class' => '/kundenkarte/class/myobject.class.php',
|
||||
// 'objectname' => 'MyObject',
|
||||
// 'method' => 'doScheduledJob',
|
||||
// 'parameters' => '',
|
||||
// 'comment' => 'Comment',
|
||||
// 'frequency' => 2,
|
||||
// 'unitfrequency' => 3600,
|
||||
// 'status' => 0,
|
||||
// 'test' => 'isModEnabled("kundenkarte")',
|
||||
// 'priority' => 50,
|
||||
// ),
|
||||
);
|
||||
/* END MODULEBUILDER CRON */
|
||||
// Example: $this->cronjobs=array(
|
||||
// 0=>array('label'=>'My label', 'jobtype'=>'method', 'class'=>'/dir/class/file.class.php', 'objectname'=>'MyClass', 'method'=>'myMethod', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>2, 'unitfrequency'=>3600, 'status'=>0, 'test'=>'isModEnabled("kundenkarte")', 'priority'=>50),
|
||||
// 1=>array('label'=>'My label', 'jobtype'=>'command', 'command'=>'', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>1, 'unitfrequency'=>3600*24, 'status'=>0, 'test'=>'isModEnabled("kundenkarte")', 'priority'=>50)
|
||||
// );
|
||||
|
||||
// Permissions provided by this module
|
||||
$this->rights = array();
|
||||
$r = 0;
|
||||
// Add here entries to declare new permissions
|
||||
/* BEGIN MODULEBUILDER PERMISSIONS */
|
||||
|
||||
// Read permission
|
||||
$this->rights[$r][0] = $this->numero . sprintf("%02d", 1);
|
||||
$this->rights[$r][1] = 'Read KundenKarte data';
|
||||
$this->rights[$r][4] = 'read';
|
||||
$this->rights[$r][5] = '';
|
||||
$r++;
|
||||
|
||||
// Write permission
|
||||
$this->rights[$r][0] = $this->numero . sprintf("%02d", 2);
|
||||
$this->rights[$r][1] = 'Create/Update KundenKarte data';
|
||||
$this->rights[$r][4] = 'write';
|
||||
$this->rights[$r][5] = '';
|
||||
$r++;
|
||||
|
||||
// Delete permission
|
||||
$this->rights[$r][0] = $this->numero . sprintf("%02d", 3);
|
||||
$this->rights[$r][1] = 'Delete KundenKarte data';
|
||||
$this->rights[$r][4] = 'delete';
|
||||
$this->rights[$r][5] = '';
|
||||
$r++;
|
||||
|
||||
// Admin permission for managing types and systems
|
||||
$this->rights[$r][0] = $this->numero . sprintf("%02d", 4);
|
||||
$this->rights[$r][1] = 'Administer KundenKarte settings';
|
||||
$this->rights[$r][4] = 'admin';
|
||||
$this->rights[$r][5] = '';
|
||||
$r++;
|
||||
|
||||
/* END MODULEBUILDER PERMISSIONS */
|
||||
|
||||
|
||||
// Main menu entries to add
|
||||
$this->menu = array();
|
||||
$r = 0;
|
||||
// Add here entries to declare new menus
|
||||
/* BEGIN MODULEBUILDER TOPMENU */
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => '', // Will be stored into mainmenu + leftmenu. Use '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
|
||||
'type' => 'top', // This is a Top menu entry
|
||||
'titre' => 'ModuleKundenKarteName',
|
||||
'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle"'),
|
||||
'mainmenu' => 'kundenkarte',
|
||||
'leftmenu' => '',
|
||||
'url' => '/kundenkarte/kundenkarteindex.php',
|
||||
'langs' => 'kundenkarte@kundenkarte', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("kundenkarte")', // Define condition to show or hide menu entry. Use 'isModEnabled("kundenkarte")' if entry must be visible if module is enabled.
|
||||
'perms' => '1', // Use 'perms'=>'$user->hasRight("kundenkarte", "myobject", "read")' if you want your menu with a permission rules
|
||||
'target' => '',
|
||||
'user' => 2, // 0=Menu for internal users, 1=external users, 2=both
|
||||
);
|
||||
/* END MODULEBUILDER TOPMENU */
|
||||
|
||||
/* BEGIN MODULEBUILDER LEFTMENU */
|
||||
// Admin submenu: Manage Systems
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=kundenkarte',
|
||||
'type' => 'left',
|
||||
'titre' => 'AnlagenSystems',
|
||||
'prefix' => img_picto('', 'fa-cogs', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'kundenkarte',
|
||||
'leftmenu' => 'kundenkarte_systems',
|
||||
'url' => '/kundenkarte/admin/anlage_systems.php',
|
||||
'langs' => 'kundenkarte@kundenkarte',
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("kundenkarte")',
|
||||
'perms' => '$user->hasRight("kundenkarte", "admin")',
|
||||
'target' => '',
|
||||
'user' => 0,
|
||||
);
|
||||
|
||||
// Admin submenu: Manage Element Types
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=kundenkarte',
|
||||
'type' => 'left',
|
||||
'titre' => 'AnlagenTypes',
|
||||
'prefix' => img_picto('', 'fa-list', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'kundenkarte',
|
||||
'leftmenu' => 'kundenkarte_types',
|
||||
'url' => '/kundenkarte/admin/anlage_types.php',
|
||||
'langs' => 'kundenkarte@kundenkarte',
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("kundenkarte")',
|
||||
'perms' => '$user->hasRight("kundenkarte", "admin")',
|
||||
'target' => '',
|
||||
'user' => 0,
|
||||
);
|
||||
/* END MODULEBUILDER LEFTMENU */
|
||||
|
||||
|
||||
// Exports profiles provided by this module
|
||||
$r = 0;
|
||||
/* BEGIN MODULEBUILDER EXPORT MYOBJECT */
|
||||
/*
|
||||
$langs->load("kundenkarte@kundenkarte");
|
||||
$this->export_code[$r] = $this->rights_class.'_'.$r;
|
||||
$this->export_label[$r] = 'MyObjectLines'; // Translation key (used only if key ExportDataset_xxx_z not found)
|
||||
$this->export_icon[$r] = $this->picto;
|
||||
// Define $this->export_fields_array, $this->export_TypeFields_array and $this->export_entities_array
|
||||
$keyforclass = 'MyObject'; $keyforclassfile='/kundenkarte/class/myobject.class.php'; $keyforelement='myobject@kundenkarte';
|
||||
include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php';
|
||||
//$this->export_fields_array[$r]['t.fieldtoadd']='FieldToAdd'; $this->export_TypeFields_array[$r]['t.fieldtoadd']='Text';
|
||||
//unset($this->export_fields_array[$r]['t.fieldtoremove']);
|
||||
//$keyforclass = 'MyObjectLine'; $keyforclassfile='/kundenkarte/class/myobject.class.php'; $keyforelement='myobjectline@kundenkarte'; $keyforalias='tl';
|
||||
//include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php';
|
||||
$keyforselect='myobject'; $keyforaliasextra='extra'; $keyforelement='myobject@kundenkarte';
|
||||
include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
|
||||
//$keyforselect='myobjectline'; $keyforaliasextra='extraline'; $keyforelement='myobjectline@kundenkarte';
|
||||
//include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
|
||||
//$this->export_dependencies_array[$r] = array('myobjectline' => array('tl.rowid','tl.ref')); // To force to activate one or several fields if we select some fields that need same (like to select a unique key if we ask a field of a child to avoid the DISTINCT to discard them, or for computed field than need several other fields)
|
||||
//$this->export_special_array[$r] = array('t.field' => '...');
|
||||
//$this->export_examplevalues_array[$r] = array('t.field' => 'Example');
|
||||
//$this->export_help_array[$r] = array('t.field' => 'FieldDescHelp');
|
||||
$this->export_sql_start[$r]='SELECT DISTINCT ';
|
||||
$this->export_sql_end[$r] =' FROM '.$this->db->prefix().'kundenkarte_myobject as t';
|
||||
//$this->export_sql_end[$r] .=' LEFT JOIN '.$this->db->prefix().'kundenkarte_myobject_line as tl ON tl.fk_myobject = t.rowid';
|
||||
$this->export_sql_end[$r] .=' WHERE 1 = 1';
|
||||
$this->export_sql_end[$r] .=' AND t.entity IN ('.getEntity('myobject').')';
|
||||
$r++; */
|
||||
/* END MODULEBUILDER EXPORT MYOBJECT */
|
||||
|
||||
// Imports profiles provided by this module
|
||||
$r = 0;
|
||||
/* BEGIN MODULEBUILDER IMPORT MYOBJECT */
|
||||
/*
|
||||
$langs->load("kundenkarte@kundenkarte");
|
||||
$this->import_code[$r] = $this->rights_class.'_'.$r;
|
||||
$this->import_label[$r] = 'MyObjectLines'; // Translation key (used only if key ExportDataset_xxx_z not found)
|
||||
$this->import_icon[$r] = $this->picto;
|
||||
$this->import_tables_array[$r] = array('t' => $this->db->prefix().'kundenkarte_myobject', 'extra' => $this->db->prefix().'kundenkarte_myobject_extrafields');
|
||||
$this->import_tables_creator_array[$r] = array('t' => 'fk_user_author'); // Fields to store import user id
|
||||
$import_sample = array();
|
||||
$keyforclass = 'MyObject'; $keyforclassfile='/kundenkarte/class/myobject.class.php'; $keyforelement='myobject@kundenkarte';
|
||||
include DOL_DOCUMENT_ROOT.'/core/commonfieldsinimport.inc.php';
|
||||
$import_extrafield_sample = array();
|
||||
$keyforselect='myobject'; $keyforaliasextra='extra'; $keyforelement='myobject@kundenkarte';
|
||||
include DOL_DOCUMENT_ROOT.'/core/extrafieldsinimport.inc.php';
|
||||
$this->import_fieldshidden_array[$r] = array('extra.fk_object' => 'lastrowid-'.$this->db->prefix().'kundenkarte_myobject');
|
||||
$this->import_regex_array[$r] = array();
|
||||
$this->import_examplevalues_array[$r] = array_merge($import_sample, $import_extrafield_sample);
|
||||
$this->import_updatekeys_array[$r] = array('t.ref' => 'Ref');
|
||||
$this->import_convertvalue_array[$r] = array(
|
||||
't.ref' => array(
|
||||
'rule'=>'getrefifauto',
|
||||
'class'=>(!getDolGlobalString('KUNDENKARTE_MYOBJECT_ADDON') ? 'mod_myobject_standard' : getDolGlobalString('KUNDENKARTE_MYOBJECT_ADDON')),
|
||||
'path'=>"/core/modules/kundenkarte/".(!getDolGlobalString('KUNDENKARTE_MYOBJECT_ADDON') ? 'mod_myobject_standard' : getDolGlobalString('KUNDENKARTE_MYOBJECT_ADDON')).'.php',
|
||||
'classobject'=>'MyObject',
|
||||
'pathobject'=>'/kundenkarte/class/myobject.class.php',
|
||||
),
|
||||
't.fk_soc' => array('rule' => 'fetchidfromref', 'file' => '/societe/class/societe.class.php', 'class' => 'Societe', 'method' => 'fetch', 'element' => 'ThirdParty'),
|
||||
't.fk_user_valid' => array('rule' => 'fetchidfromref', 'file' => '/user/class/user.class.php', 'class' => 'User', 'method' => 'fetch', 'element' => 'user'),
|
||||
't.fk_mode_reglement' => array('rule' => 'fetchidfromcodeorlabel', 'file' => '/compta/paiement/class/cpaiement.class.php', 'class' => 'Cpaiement', 'method' => 'fetch', 'element' => 'cpayment'),
|
||||
);
|
||||
$this->import_run_sql_after_array[$r] = array();
|
||||
$r++; */
|
||||
/* END MODULEBUILDER IMPORT MYOBJECT */
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when module is enabled.
|
||||
* The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database.
|
||||
* It also creates data directories
|
||||
*
|
||||
* @param string $options Options when enabling module ('', 'noboxes')
|
||||
* @return int<-1,1> 1 if OK, <=0 if KO
|
||||
*/
|
||||
public function init($options = '')
|
||||
{
|
||||
global $conf, $langs;
|
||||
|
||||
// Create tables of module at module activation
|
||||
//$result = $this->_load_tables('/install/mysql/', 'kundenkarte');
|
||||
$result = $this->_load_tables('/kundenkarte/sql/');
|
||||
if ($result < 0) {
|
||||
return -1; // Do not activate module if error 'not allowed' returned when loading module SQL queries (the _load_table run sql with run_sql with the error allowed parameter set to 'default')
|
||||
}
|
||||
|
||||
// Create extrafields during init
|
||||
//include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
|
||||
//$extrafields = new ExtraFields($this->db);
|
||||
//$result0=$extrafields->addExtraField('kundenkarte_separator1', "Separator 1", 'separator', 1, 0, 'thirdparty', 0, 0, '', array('options'=>array(1=>1)), 1, '', 1, 0, '', '', 'kundenkarte@kundenkarte', 'isModEnabled("kundenkarte")');
|
||||
//$result1=$extrafields->addExtraField('kundenkarte_myattr1', "New Attr 1 label", 'boolean', 1, 3, 'thirdparty', 0, 0, '', '', 1, '', -1, 0, '', '', 'kundenkarte@kundenkarte', 'isModEnabled("kundenkarte")');
|
||||
//$result2=$extrafields->addExtraField('kundenkarte_myattr2', "New Attr 2 label", 'varchar', 1, 10, 'project', 0, 0, '', '', 1, '', -1, 0, '', '', 'kundenkarte@kundenkarte', 'isModEnabled("kundenkarte")');
|
||||
//$result3=$extrafields->addExtraField('kundenkarte_myattr3', "New Attr 3 label", 'varchar', 1, 10, 'bank_account', 0, 0, '', '', 1, '', -1, 0, '', '', 'kundenkarte@kundenkarte', 'isModEnabled("kundenkarte")');
|
||||
//$result4=$extrafields->addExtraField('kundenkarte_myattr4', "New Attr 4 label", 'select', 1, 3, 'thirdparty', 0, 1, '', array('options'=>array('code1'=>'Val1','code2'=>'Val2','code3'=>'Val3')), 1,'', -1, 0, '', '', 'kundenkarte@kundenkarte', 'isModEnabled("kundenkarte")');
|
||||
//$result5=$extrafields->addExtraField('kundenkarte_myattr5', "New Attr 5 label", 'text', 1, 10, 'user', 0, 0, '', '', 1, '', -1, 0, '', '', 'kundenkarte@kundenkarte', 'isModEnabled("kundenkarte")');
|
||||
|
||||
// Permissions
|
||||
$this->remove($options);
|
||||
|
||||
$sql = array();
|
||||
|
||||
// Document templates
|
||||
$moduledir = dol_sanitizeFileName('kundenkarte');
|
||||
$myTmpObjects = array();
|
||||
$myTmpObjects['MyObject'] = array('includerefgeneration' => 0, 'includedocgeneration' => 0);
|
||||
|
||||
foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) {
|
||||
if ($myTmpObjectArray['includerefgeneration']) {
|
||||
$src = DOL_DOCUMENT_ROOT.'/install/doctemplates/'.$moduledir.'/template_myobjects.odt';
|
||||
$dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/'.$moduledir;
|
||||
$dest = $dirodt.'/template_myobjects.odt';
|
||||
|
||||
if (file_exists($src) && !file_exists($dest)) {
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||
dol_mkdir($dirodt);
|
||||
$result = dol_copy($src, $dest, '0', 0);
|
||||
if ($result < 0) {
|
||||
$langs->load("errors");
|
||||
$this->error = $langs->trans('ErrorFailToCopyFile', $src, $dest);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$sql = array_merge($sql, array(
|
||||
"DELETE FROM ".$this->db->prefix()."document_model WHERE nom = 'standard_".strtolower($myTmpObjectKey)."' AND type = '".$this->db->escape(strtolower($myTmpObjectKey))."' AND entity = ".((int) $conf->entity),
|
||||
"INSERT INTO ".$this->db->prefix()."document_model (nom, type, entity) VALUES('standard_".strtolower($myTmpObjectKey)."', '".$this->db->escape(strtolower($myTmpObjectKey))."', ".((int) $conf->entity).")",
|
||||
"DELETE FROM ".$this->db->prefix()."document_model WHERE nom = 'generic_".strtolower($myTmpObjectKey)."_odt' AND type = '".$this->db->escape(strtolower($myTmpObjectKey))."' AND entity = ".((int) $conf->entity),
|
||||
"INSERT INTO ".$this->db->prefix()."document_model (nom, type, entity) VALUES('generic_".strtolower($myTmpObjectKey)."_odt', '".$this->db->escape(strtolower($myTmpObjectKey))."', ".((int) $conf->entity).")"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_init($sql, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when module is disabled.
|
||||
* Remove from database constants, boxes and permissions from Dolibarr database.
|
||||
* Data directories are not deleted
|
||||
*
|
||||
* @param string $options Options when enabling module ('', 'noboxes')
|
||||
* @return int<-1,1> 1 if OK, <=0 if KO
|
||||
*/
|
||||
public function remove($options = '')
|
||||
{
|
||||
$sql = array();
|
||||
return $this->_remove($sql, $options);
|
||||
}
|
||||
}
|
||||
832
css/kundenkarte.css
Executable file
832
css/kundenkarte.css
Executable file
|
|
@ -0,0 +1,832 @@
|
|||
/**
|
||||
* KundenKarte Module Styles
|
||||
* Copyright (C) 2026 Alles Watt lauft
|
||||
*/
|
||||
|
||||
/* ========================================
|
||||
TREE STRUCTURE
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-tree {
|
||||
font-family: inherit;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-node {
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-node::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-node::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 12px;
|
||||
width: 15px;
|
||||
height: 1px;
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-node:last-child::before {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #e0e0e0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-item:hover {
|
||||
background: #f0f0f0;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-item.selected {
|
||||
background: #e3f2fd;
|
||||
border-color: #2196f3;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-toggle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-toggle:hover {
|
||||
color: #333;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-toggle .fa-chevron-down {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-toggle.collapsed .fa-chevron-down {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.kundenkarte-tree-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-label {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-label-info {
|
||||
font-weight: normal;
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-info {
|
||||
color: #888;
|
||||
font-size: 0.9em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-actions {
|
||||
display: none;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-item:hover .kundenkarte-tree-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-actions a {
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-actions a:hover {
|
||||
background: #ddd;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-children {
|
||||
margin-left: 10px;
|
||||
border-left: 1px dashed #ccc;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-children.collapsed {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Tree - Type Badge */
|
||||
.kundenkarte-tree-type {
|
||||
font-size: 0.75em;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
background: #e0e0e0;
|
||||
color: #555;
|
||||
margin-left: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Tree - Location */
|
||||
.kundenkarte-tree-location {
|
||||
font-size: 0.85em;
|
||||
color: #666;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-location i {
|
||||
color: #e74c3c;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Tree - File Indicators */
|
||||
.kundenkarte-tree-files {
|
||||
display: inline-flex;
|
||||
gap: 3px;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-file-badge {
|
||||
font-size: 0.8em;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-file-images {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-file-docs {
|
||||
background: #fff3e0;
|
||||
color: #f57c00;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
HOVER TOOLTIP
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-tooltip {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
padding: 15px;
|
||||
min-width: 300px;
|
||||
max-width: 450px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-title {
|
||||
font-weight: 600;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-type {
|
||||
color: #888;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-fields {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 6px 12px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-field-label {
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-field-value {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-images {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #eee;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.kundenkarte-tooltip-thumb {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
SYSTEM TABS
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-system-tabs-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.kundenkarte-system-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-controls {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.kundenkarte-tree-controls .button {
|
||||
padding: 4px 10px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.kundenkarte-system-tab {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px 4px 0 0;
|
||||
background: #f5f5f5;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.kundenkarte-system-tab:hover {
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.kundenkarte-system-tab.active {
|
||||
background: #fff;
|
||||
border-bottom-color: #fff;
|
||||
margin-bottom: -2px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.kundenkarte-system-tab-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.kundenkarte-system-tab-add {
|
||||
background: transparent;
|
||||
border-style: dashed;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.kundenkarte-system-tab-add:hover {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
FAVORITE PRODUCTS
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-favorites {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kundenkarte-favorites-add {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.kundenkarte-favorites-add .product-search {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kundenkarte-favorites-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kundenkarte-favorites-table th {
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
background: #f5f5f5;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.kundenkarte-favorites-table td {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.kundenkarte-favorites-table tr:hover {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.kundenkarte-favorites-qty {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.kundenkarte-favorites-actions {
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #ddd;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
ELEMENT FORM
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-element-form {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.kundenkarte-element-form .field-row {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.kundenkarte-element-form label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kundenkarte-element-form .required::after {
|
||||
content: ' *';
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
FILE GALLERY
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-files {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.kundenkarte-files-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.kundenkarte-file-item {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.kundenkarte-file-preview {
|
||||
height: 140px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.kundenkarte-file-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.kundenkarte-file-preview embed {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.kundenkarte-file-preview .fa {
|
||||
font-size: 48px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.kundenkarte-file-info {
|
||||
padding: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.kundenkarte-file-name {
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kundenkarte-file-size {
|
||||
color: #888;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.kundenkarte-file-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin-top: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.kundenkarte-file-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
background: #f0f0f0;
|
||||
color: #555;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.kundenkarte-file-btn:hover {
|
||||
background: #2196f3;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.kundenkarte-file-btn-delete {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
.kundenkarte-file-btn-delete:hover {
|
||||
background: #c00;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.kundenkarte-file-cover {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
background: #4caf50;
|
||||
color: #fff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
ICON PICKER MODAL
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.kundenkarte-modal.visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.kundenkarte-modal-content {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
||||
max-width: 600px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kundenkarte-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.kundenkarte-modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.kundenkarte-modal-close {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.kundenkarte-modal-close:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.kundenkarte-modal-body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.kundenkarte-icon-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
|
||||
gap: 8px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.kundenkarte-icon-item {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.kundenkarte-icon-item:hover {
|
||||
background: #e3f2fd;
|
||||
border-color: #2196f3;
|
||||
color: #2196f3;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.kundenkarte-icon-item:hover .kundenkarte-icon-delete {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.kundenkarte-icon-delete:hover {
|
||||
background: #900 !important;
|
||||
}
|
||||
|
||||
/* Icon picker button in forms */
|
||||
.kundenkarte-icon-picker-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.kundenkarte-icon-picker-wrapper input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kundenkarte-icon-picker-btn {
|
||||
padding: 5px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background: #f5f5f5;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.kundenkarte-icon-picker-btn:hover {
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.kundenkarte-icon-preview {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
TAG SELECTOR (Admin)
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-selected-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.kundenkarte-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 10px;
|
||||
background: #e3f2fd;
|
||||
border: 1px solid #90caf9;
|
||||
border-radius: 16px;
|
||||
font-size: 0.9em;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.kundenkarte-tag-remove {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
line-height: 1;
|
||||
color: #1976d2;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.kundenkarte-tag-remove:hover {
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
IMAGES POPUP
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-images-popup {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.kundenkarte-images-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
gap: 8px;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.kundenkarte-images-thumb {
|
||||
display: block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #ddd;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.kundenkarte-images-thumb:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
border-color: #2196f3;
|
||||
}
|
||||
|
||||
.kundenkarte-images-thumb img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
DOCUMENTS POPUP - Visual cards with icons
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-docs-popup {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.kundenkarte-docs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
gap: 8px;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.kundenkarte-docs-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px 5px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e0e0e0;
|
||||
background: #f9f9f9;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.kundenkarte-docs-card:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
border-color: #2196f3;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.kundenkarte-docs-card-icon {
|
||||
font-size: 36px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.kundenkarte-docs-card-name {
|
||||
font-size: 0.75em;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 70px;
|
||||
}
|
||||
|
||||
/* Tree icon clickable */
|
||||
.kundenkarte-tree-icon.kundenkarte-tooltip-trigger {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* File badges as links */
|
||||
a.kundenkarte-tree-file-badge {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a.kundenkarte-tree-file-badge:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
PDF PREVIEW (smaller, no toolbar)
|
||||
======================================== */
|
||||
|
||||
.kundenkarte-pdf-preview-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.kundenkarte-pdf-preview-frame {
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
border: none;
|
||||
transform: scale(0.5);
|
||||
transform-origin: top left;
|
||||
pointer-events: none;
|
||||
}
|
||||
14
img/README.md
Executable file
14
img/README.md
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
Directory for module image files
|
||||
--------------------------------
|
||||
|
||||
You can put here the .png files of your module:
|
||||
|
||||
|
||||
If the picto of your module is an image (property $picto has been set to 'kundenkarte.png@kundenkarte', you can put into this
|
||||
directory a .png file called *object_kundenkarte.png* (16x16 or 32x32 pixels)
|
||||
|
||||
|
||||
If the picto of an object is an image (property $picto of the object.class.php has been set to 'myobject.png@kundenkarte', then you can put into this
|
||||
directory a .png file called *object_myobject.png* (16x16 or 32x32 pixels)
|
||||
|
||||
950
js/kundenkarte.js
Executable file
950
js/kundenkarte.js
Executable file
|
|
@ -0,0 +1,950 @@
|
|||
/**
|
||||
* KundenKarte Module JavaScript
|
||||
* Copyright (C) 2026 Alles Watt lauft
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Namespace
|
||||
window.KundenKarte = window.KundenKarte || {};
|
||||
|
||||
// Get base URL for AJAX calls
|
||||
var baseUrl = (typeof DOL_URL_ROOT !== 'undefined') ? DOL_URL_ROOT : '';
|
||||
if (!baseUrl) {
|
||||
// Try to detect from script src
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var src = scripts[i].src;
|
||||
if (src && src.indexOf('/kundenkarte/js/kundenkarte.js') > -1) {
|
||||
baseUrl = src.replace('/custom/kundenkarte/js/kundenkarte.js', '').replace(/\?.*$/, '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tree Component
|
||||
*/
|
||||
KundenKarte.Tree = {
|
||||
tooltipTimeout: null,
|
||||
hideTimeout: null,
|
||||
currentTooltip: null,
|
||||
currentItem: null,
|
||||
|
||||
init: function() {
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
bindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
// Toggle tree nodes - MUST use stopImmediatePropagation for delegated handlers on same element
|
||||
$(document).on('click', '.kundenkarte-tree-toggle', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
var $toggle = $(this);
|
||||
var $node = $toggle.closest('.kundenkarte-tree-node');
|
||||
var $children = $node.children('.kundenkarte-tree-children');
|
||||
|
||||
$toggle.toggleClass('collapsed');
|
||||
$children.toggleClass('collapsed');
|
||||
});
|
||||
|
||||
// Expand all nodes
|
||||
$(document).on('click', '#btn-expand-all', function(e) {
|
||||
e.preventDefault();
|
||||
self.expandAll();
|
||||
});
|
||||
|
||||
// Collapse all nodes
|
||||
$(document).on('click', '#btn-collapse-all', function(e) {
|
||||
e.preventDefault();
|
||||
self.collapseAll();
|
||||
});
|
||||
|
||||
// Hover tooltip on ICON only - show after delay
|
||||
$(document).on('mouseenter', '.kundenkarte-tooltip-trigger', function(e) {
|
||||
var $trigger = $(this);
|
||||
var anlageId = $trigger.data('anlage-id');
|
||||
|
||||
if (!anlageId) return;
|
||||
|
||||
// Cancel any pending hide
|
||||
clearTimeout(self.hideTimeout);
|
||||
self.hideTimeout = null;
|
||||
|
||||
self.currentItem = $trigger;
|
||||
|
||||
self.tooltipTimeout = setTimeout(function() {
|
||||
self.showTooltip($trigger, anlageId);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Hide tooltip when leaving icon
|
||||
$(document).on('mouseleave', '.kundenkarte-tooltip-trigger', function() {
|
||||
clearTimeout(self.tooltipTimeout);
|
||||
self.tooltipTimeout = null;
|
||||
self.currentItem = null;
|
||||
|
||||
// Hide after short delay (allows moving to tooltip)
|
||||
self.hideTimeout = setTimeout(function() {
|
||||
self.hideTooltip();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Images tooltip on hover
|
||||
$(document).on('mouseenter', '.kundenkarte-images-trigger', function(e) {
|
||||
var $trigger = $(this);
|
||||
var anlageId = $trigger.data('anlage-id');
|
||||
|
||||
if (!anlageId) return;
|
||||
|
||||
clearTimeout(self.hideTimeout);
|
||||
self.hideTimeout = null;
|
||||
|
||||
self.tooltipTimeout = setTimeout(function() {
|
||||
self.showImagesPopup($trigger, anlageId);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
$(document).on('mouseleave', '.kundenkarte-images-trigger', function() {
|
||||
clearTimeout(self.tooltipTimeout);
|
||||
self.tooltipTimeout = null;
|
||||
|
||||
self.hideTimeout = setTimeout(function() {
|
||||
self.hideTooltip();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Documents tooltip on hover
|
||||
$(document).on('mouseenter', '.kundenkarte-docs-trigger', function(e) {
|
||||
var $trigger = $(this);
|
||||
var anlageId = $trigger.data('anlage-id');
|
||||
|
||||
if (!anlageId) return;
|
||||
|
||||
clearTimeout(self.hideTimeout);
|
||||
self.hideTimeout = null;
|
||||
|
||||
self.tooltipTimeout = setTimeout(function() {
|
||||
self.showDocsPopup($trigger, anlageId);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
$(document).on('mouseleave', '.kundenkarte-docs-trigger', function() {
|
||||
clearTimeout(self.tooltipTimeout);
|
||||
self.tooltipTimeout = null;
|
||||
|
||||
self.hideTimeout = setTimeout(function() {
|
||||
self.hideTooltip();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Keep tooltip visible when hovering over it
|
||||
$(document).on('mouseenter', '#kundenkarte-tooltip', function() {
|
||||
clearTimeout(self.hideTimeout);
|
||||
self.hideTimeout = null;
|
||||
});
|
||||
|
||||
// Hide when leaving tooltip
|
||||
$(document).on('mouseleave', '#kundenkarte-tooltip', function() {
|
||||
self.hideTooltip();
|
||||
});
|
||||
|
||||
// Select item
|
||||
$(document).on('click', '.kundenkarte-tree-item', function(e) {
|
||||
if ($(e.target).closest('.kundenkarte-tree-toggle, .kundenkarte-tree-actions, .kundenkarte-tree-files').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('.kundenkarte-tree-item').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
|
||||
var anlageId = $(this).data('anlage-id');
|
||||
if (anlageId) {
|
||||
$(document).trigger('kundenkarte:element:selected', [anlageId]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showTooltip: function($item, anlageId) {
|
||||
var self = this;
|
||||
|
||||
// Get tooltip data from data attribute (faster than AJAX)
|
||||
var tooltipDataStr = $item.attr('data-tooltip');
|
||||
if (!tooltipDataStr) {
|
||||
console.log('No tooltip data for anlage', anlageId);
|
||||
return;
|
||||
}
|
||||
|
||||
var data;
|
||||
try {
|
||||
data = JSON.parse(tooltipDataStr);
|
||||
} catch(e) {
|
||||
console.error('Failed to parse tooltip JSON:', e, tooltipDataStr);
|
||||
return;
|
||||
}
|
||||
|
||||
var html = self.buildTooltipHtml(data);
|
||||
var $tooltip = $('#kundenkarte-tooltip');
|
||||
|
||||
if (!$tooltip.length) {
|
||||
$tooltip = $('<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>');
|
||||
$('body').append($tooltip);
|
||||
}
|
||||
|
||||
$tooltip.html(html);
|
||||
|
||||
// Position tooltip
|
||||
var offset = $item.offset();
|
||||
var itemWidth = $item.outerWidth();
|
||||
var windowWidth = $(window).width();
|
||||
var scrollTop = $(window).scrollTop();
|
||||
|
||||
// First show to calculate width
|
||||
$tooltip.css({ visibility: 'hidden', display: 'block' });
|
||||
var tooltipWidth = $tooltip.outerWidth();
|
||||
var tooltipHeight = $tooltip.outerHeight();
|
||||
$tooltip.css({ visibility: '', display: '' });
|
||||
|
||||
var left = offset.left + itemWidth + 10;
|
||||
if (left + tooltipWidth > windowWidth - 20) {
|
||||
left = offset.left - tooltipWidth - 10;
|
||||
}
|
||||
if (left < 10) {
|
||||
left = 10;
|
||||
}
|
||||
|
||||
var top = offset.top;
|
||||
// Prevent tooltip from going below viewport
|
||||
if (top + tooltipHeight > scrollTop + $(window).height() - 20) {
|
||||
top = scrollTop + $(window).height() - tooltipHeight - 20;
|
||||
}
|
||||
|
||||
$tooltip.css({
|
||||
top: top,
|
||||
left: left
|
||||
}).addClass('visible').show();
|
||||
|
||||
self.currentTooltip = $tooltip;
|
||||
},
|
||||
|
||||
hideTooltip: function() {
|
||||
clearTimeout(this.hideTimeout);
|
||||
this.hideTimeout = null;
|
||||
var $tooltip = $('#kundenkarte-tooltip');
|
||||
if ($tooltip.length) {
|
||||
$tooltip.removeClass('visible').hide();
|
||||
}
|
||||
this.currentTooltip = null;
|
||||
},
|
||||
|
||||
showImagesPopup: function($trigger, anlageId) {
|
||||
var self = this;
|
||||
|
||||
// Load images via AJAX - use absolute path
|
||||
$.ajax({
|
||||
url: baseUrl + '/custom/kundenkarte/ajax/anlage_images.php',
|
||||
data: { anlage_id: anlageId },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (!response.images || response.images.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '<div class="kundenkarte-images-popup">';
|
||||
html += '<div class="kundenkarte-images-grid">';
|
||||
for (var i = 0; i < response.images.length; i++) {
|
||||
var img = response.images[i];
|
||||
html += '<a href="' + img.url + '" target="_blank" class="kundenkarte-images-thumb">';
|
||||
html += '<img src="' + img.thumb + '" alt="' + self.escapeHtml(img.name) + '">';
|
||||
html += '</a>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
var $tooltip = $('#kundenkarte-tooltip');
|
||||
if (!$tooltip.length) {
|
||||
$tooltip = $('<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>');
|
||||
$('body').append($tooltip);
|
||||
}
|
||||
|
||||
$tooltip.html(html);
|
||||
|
||||
// Position tooltip
|
||||
var offset = $trigger.offset();
|
||||
var windowWidth = $(window).width();
|
||||
var scrollTop = $(window).scrollTop();
|
||||
|
||||
$tooltip.css({ visibility: 'hidden', display: 'block' });
|
||||
var tooltipWidth = $tooltip.outerWidth();
|
||||
var tooltipHeight = $tooltip.outerHeight();
|
||||
$tooltip.css({ visibility: '', display: '' });
|
||||
|
||||
var left = offset.left + $trigger.outerWidth() + 10;
|
||||
if (left + tooltipWidth > windowWidth - 20) {
|
||||
left = offset.left - tooltipWidth - 10;
|
||||
}
|
||||
if (left < 10) left = 10;
|
||||
|
||||
var top = offset.top;
|
||||
if (top + tooltipHeight > scrollTop + $(window).height() - 20) {
|
||||
top = scrollTop + $(window).height() - tooltipHeight - 20;
|
||||
}
|
||||
|
||||
$tooltip.css({ top: top, left: left }).addClass('visible').show();
|
||||
self.currentTooltip = $tooltip;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showDocsPopup: function($trigger, anlageId) {
|
||||
var self = this;
|
||||
|
||||
// Load documents via AJAX
|
||||
$.ajax({
|
||||
url: baseUrl + '/custom/kundenkarte/ajax/anlage_docs.php',
|
||||
data: { anlage_id: anlageId },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (!response.docs || response.docs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Visual document cards with icons
|
||||
var html = '<div class="kundenkarte-docs-popup">';
|
||||
html += '<div class="kundenkarte-docs-grid">';
|
||||
for (var i = 0; i < response.docs.length; i++) {
|
||||
var doc = response.docs[i];
|
||||
var iconClass = doc.type === 'pdf' ? 'fa-file-pdf-o' : 'fa-file-text-o';
|
||||
var iconColor = doc.type === 'pdf' ? '#e74c3c' : '#f39c12';
|
||||
html += '<a href="' + doc.url + '" target="_blank" class="kundenkarte-docs-card">';
|
||||
html += '<div class="kundenkarte-docs-card-icon" style="color:' + iconColor + '">';
|
||||
html += '<i class="fa ' + iconClass + '"></i>';
|
||||
html += '</div>';
|
||||
html += '<div class="kundenkarte-docs-card-name">' + self.escapeHtml(doc.name) + '</div>';
|
||||
html += '</a>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
var $tooltip = $('#kundenkarte-tooltip');
|
||||
if (!$tooltip.length) {
|
||||
$tooltip = $('<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>');
|
||||
$('body').append($tooltip);
|
||||
}
|
||||
|
||||
$tooltip.html(html);
|
||||
|
||||
// Position tooltip
|
||||
var offset = $trigger.offset();
|
||||
var windowWidth = $(window).width();
|
||||
var scrollTop = $(window).scrollTop();
|
||||
|
||||
$tooltip.css({ visibility: 'hidden', display: 'block' });
|
||||
var tooltipWidth = $tooltip.outerWidth();
|
||||
var tooltipHeight = $tooltip.outerHeight();
|
||||
$tooltip.css({ visibility: '', display: '' });
|
||||
|
||||
var left = offset.left + $trigger.outerWidth() + 10;
|
||||
if (left + tooltipWidth > windowWidth - 20) {
|
||||
left = offset.left - tooltipWidth - 10;
|
||||
}
|
||||
if (left < 10) left = 10;
|
||||
|
||||
var top = offset.top;
|
||||
if (top + tooltipHeight > scrollTop + $(window).height() - 20) {
|
||||
top = scrollTop + $(window).height() - tooltipHeight - 20;
|
||||
}
|
||||
|
||||
$tooltip.css({ top: top, left: left }).addClass('visible').show();
|
||||
self.currentTooltip = $tooltip;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
buildTooltipHtml: function(data) {
|
||||
var html = '<div class="kundenkarte-tooltip-header">';
|
||||
html += '<span class="kundenkarte-tooltip-icon"><i class="fa ' + (data.picto || 'fa-cube') + '"></i></span>';
|
||||
html += '<div>';
|
||||
html += '<div class="kundenkarte-tooltip-title">' + this.escapeHtml(data.label || '') + '</div>';
|
||||
html += '<div class="kundenkarte-tooltip-type">' + this.escapeHtml(data.type || data.type_label || '') + '</div>';
|
||||
html += '</div></div>';
|
||||
|
||||
html += '<div class="kundenkarte-tooltip-fields">';
|
||||
|
||||
if (data.location) {
|
||||
html += '<span class="kundenkarte-tooltip-field-label"><i class="fa fa-map-marker"></i> Standort:</span>';
|
||||
html += '<span class="kundenkarte-tooltip-field-value">' + this.escapeHtml(data.location) + '</span>';
|
||||
}
|
||||
|
||||
if (data.manufacturer) {
|
||||
html += '<span class="kundenkarte-tooltip-field-label">Hersteller:</span>';
|
||||
html += '<span class="kundenkarte-tooltip-field-value">' + this.escapeHtml(data.manufacturer) + '</span>';
|
||||
}
|
||||
|
||||
if (data.model) {
|
||||
html += '<span class="kundenkarte-tooltip-field-label">Modell:</span>';
|
||||
html += '<span class="kundenkarte-tooltip-field-value">' + this.escapeHtml(data.model) + '</span>';
|
||||
}
|
||||
|
||||
if (data.serial_number) {
|
||||
html += '<span class="kundenkarte-tooltip-field-label">Seriennummer:</span>';
|
||||
html += '<span class="kundenkarte-tooltip-field-value">' + this.escapeHtml(data.serial_number) + '</span>';
|
||||
}
|
||||
|
||||
if (data.power_rating) {
|
||||
html += '<span class="kundenkarte-tooltip-field-label">Leistung:</span>';
|
||||
html += '<span class="kundenkarte-tooltip-field-value">' + this.escapeHtml(data.power_rating) + '</span>';
|
||||
}
|
||||
|
||||
if (data.installation_date) {
|
||||
html += '<span class="kundenkarte-tooltip-field-label">Installiert:</span>';
|
||||
html += '<span class="kundenkarte-tooltip-field-value">' + this.escapeHtml(data.installation_date) + '</span>';
|
||||
}
|
||||
|
||||
// Dynamic fields (from AJAX)
|
||||
if (data.fields) {
|
||||
for (var key in data.fields) {
|
||||
if (data.fields.hasOwnProperty(key) && data.fields[key].show_in_hover) {
|
||||
html += '<span class="kundenkarte-tooltip-field-label">' + this.escapeHtml(data.fields[key].label) + ':</span>';
|
||||
html += '<span class="kundenkarte-tooltip-field-value">' + this.escapeHtml(data.fields[key].value) + '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Notes
|
||||
if (data.note) {
|
||||
html += '<div class="kundenkarte-tooltip-note" style="margin-top:10px;padding-top:10px;border-top:1px solid #eee;font-size:0.9em;color:#666;">';
|
||||
html += '<i class="fa fa-sticky-note"></i> ' + this.escapeHtml(data.note);
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Images (from AJAX)
|
||||
if (data.images && data.images.length > 0) {
|
||||
html += '<div class="kundenkarte-tooltip-images">';
|
||||
for (var i = 0; i < Math.min(data.images.length, 4); i++) {
|
||||
html += '<img src="' + data.images[i].thumb_url + '" class="kundenkarte-tooltip-thumb" alt="">';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
return html;
|
||||
},
|
||||
|
||||
escapeHtml: function(text) {
|
||||
if (!text) return '';
|
||||
var div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
refresh: function(socId, systemId) {
|
||||
var $container = $('.kundenkarte-tree[data-system="' + systemId + '"]');
|
||||
if (!$container.length) return;
|
||||
|
||||
$.ajax({
|
||||
url: baseUrl + '/custom/kundenkarte/ajax/anlage_tree.php',
|
||||
data: { socid: socId, system: systemId },
|
||||
success: function(html) {
|
||||
$container.html(html);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
expandAll: function() {
|
||||
$('.kundenkarte-tree-toggle').removeClass('collapsed');
|
||||
$('.kundenkarte-tree-children').removeClass('collapsed');
|
||||
},
|
||||
|
||||
collapseAll: function() {
|
||||
$('.kundenkarte-tree-toggle').addClass('collapsed');
|
||||
$('.kundenkarte-tree-children').addClass('collapsed');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Favorite Products Component
|
||||
*/
|
||||
KundenKarte.Favorites = {
|
||||
init: function() {
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
bindEvents: function() {
|
||||
// Select all checkbox
|
||||
$(document).on('change', '#kundenkarte-select-all', function() {
|
||||
var checked = $(this).prop('checked');
|
||||
$('.kundenkarte-favorites-table input[type="checkbox"][name="selected_products[]"]').prop('checked', checked);
|
||||
KundenKarte.Favorites.updateGenerateButton();
|
||||
});
|
||||
|
||||
// Individual checkbox
|
||||
$(document).on('change', '.kundenkarte-favorites-table input[type="checkbox"][name="selected_products[]"]', function() {
|
||||
KundenKarte.Favorites.updateGenerateButton();
|
||||
});
|
||||
|
||||
// Save button click
|
||||
$(document).on('click', '.kundenkarte-qty-save', function(e) {
|
||||
e.preventDefault();
|
||||
var $btn = $(this);
|
||||
var favId = $btn.data('fav-id');
|
||||
var $input = $('input.kundenkarte-favorites-qty[data-fav-id="' + favId + '"]');
|
||||
var qtyStr = $input.val().replace(',', '.');
|
||||
var qty = parseFloat(qtyStr);
|
||||
|
||||
if (!isNaN(qty) && qty > 0) {
|
||||
// Limit to 2 decimal places
|
||||
qty = Math.round(qty * 100) / 100;
|
||||
|
||||
// Format nicely
|
||||
var display = (qty % 1 === 0) ? qty.toString() : qty.toFixed(2).replace(/\.?0+$/, '');
|
||||
$input.val(display);
|
||||
|
||||
// Visual feedback
|
||||
$btn.prop('disabled', true);
|
||||
$btn.find('i').removeClass('fa-save').addClass('fa-spinner fa-spin');
|
||||
|
||||
$.ajax({
|
||||
url: baseUrl + '/custom/kundenkarte/ajax/favorite_update.php',
|
||||
method: 'POST',
|
||||
data: { id: favId, qty: qty, token: $('input[name="token"]').val() },
|
||||
success: function() {
|
||||
$btn.find('i').removeClass('fa-spinner fa-spin').addClass('fa-check').css('color', '#0a0');
|
||||
setTimeout(function() {
|
||||
$btn.find('i').removeClass('fa-check').addClass('fa-save').css('color', '');
|
||||
$btn.prop('disabled', false);
|
||||
}, 1500);
|
||||
},
|
||||
error: function() {
|
||||
$btn.find('i').removeClass('fa-spinner fa-spin').addClass('fa-exclamation-triangle').css('color', '#c00');
|
||||
setTimeout(function() {
|
||||
$btn.find('i').removeClass('fa-exclamation-triangle').addClass('fa-save').css('color', '');
|
||||
$btn.prop('disabled', false);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateGenerateButton: function() {
|
||||
var count = $('.kundenkarte-favorites-table input[type="checkbox"][name="selected_products[]"]:checked').length;
|
||||
var $btn = $('#btn-generate-order');
|
||||
|
||||
if (count > 0) {
|
||||
$btn.prop('disabled', false).text($btn.data('text').replace('%d', count));
|
||||
} else {
|
||||
$btn.prop('disabled', true).text($btn.data('text-none'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* System Tabs Component
|
||||
*/
|
||||
KundenKarte.SystemTabs = {
|
||||
init: function() {
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
bindEvents: function() {
|
||||
$(document).on('click', '.kundenkarte-system-tab:not(.active):not(.kundenkarte-system-tab-add)', function() {
|
||||
var systemId = $(this).data('system');
|
||||
KundenKarte.SystemTabs.switchTo(systemId);
|
||||
});
|
||||
},
|
||||
|
||||
switchTo: function(systemId) {
|
||||
$('.kundenkarte-system-tab').removeClass('active');
|
||||
$('.kundenkarte-system-tab[data-system="' + systemId + '"]').addClass('active');
|
||||
|
||||
$('.kundenkarte-system-content').hide();
|
||||
$('.kundenkarte-system-content[data-system="' + systemId + '"]').show();
|
||||
|
||||
// Update URL without reload
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('system', systemId);
|
||||
window.history.replaceState({}, '', url);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Icon Picker Component with Custom Icon Upload
|
||||
*/
|
||||
KundenKarte.IconPicker = {
|
||||
// Common FontAwesome icons for installations/technical use
|
||||
icons: [
|
||||
// Electrical
|
||||
'fa-bolt', 'fa-plug', 'fa-power-off', 'fa-charging-station', 'fa-battery-full', 'fa-battery-half',
|
||||
'fa-car-battery', 'fa-solar-panel', 'fa-sun', 'fa-lightbulb', 'fa-toggle-on', 'fa-toggle-off',
|
||||
// Network/Internet
|
||||
'fa-wifi', 'fa-network-wired', 'fa-server', 'fa-database', 'fa-hdd', 'fa-ethernet',
|
||||
'fa-broadcast-tower', 'fa-satellite-dish', 'fa-satellite', 'fa-signal', 'fa-rss',
|
||||
// TV/Media
|
||||
'fa-tv', 'fa-play-circle', 'fa-video', 'fa-film', 'fa-podcast', 'fa-music',
|
||||
// Temperature/Climate
|
||||
'fa-thermometer-half', 'fa-temperature-high', 'fa-temperature-low', 'fa-fire', 'fa-fire-alt',
|
||||
'fa-snowflake', 'fa-wind', 'fa-fan', 'fa-air-freshener',
|
||||
// Building/Structure
|
||||
'fa-home', 'fa-building', 'fa-warehouse', 'fa-door-open', 'fa-door-closed', 'fa-archway',
|
||||
// Devices/Hardware
|
||||
'fa-microchip', 'fa-memory', 'fa-sim-card', 'fa-sd-card', 'fa-usb', 'fa-desktop', 'fa-laptop',
|
||||
'fa-mobile-alt', 'fa-tablet-alt', 'fa-keyboard', 'fa-print', 'fa-fax',
|
||||
// Security
|
||||
'fa-shield-alt', 'fa-lock', 'fa-unlock', 'fa-key', 'fa-fingerprint', 'fa-eye', 'fa-video',
|
||||
'fa-bell', 'fa-exclamation-triangle', 'fa-user-shield',
|
||||
// Objects
|
||||
'fa-cube', 'fa-cubes', 'fa-box', 'fa-boxes', 'fa-archive', 'fa-toolbox', 'fa-tools', 'fa-wrench',
|
||||
'fa-cog', 'fa-cogs', 'fa-sliders-h',
|
||||
// Layout
|
||||
'fa-th', 'fa-th-large', 'fa-th-list', 'fa-grip-horizontal', 'fa-grip-vertical', 'fa-bars',
|
||||
'fa-stream', 'fa-layer-group', 'fa-project-diagram', 'fa-share-alt', 'fa-sitemap',
|
||||
// Arrows/Direction
|
||||
'fa-exchange-alt', 'fa-arrows-alt', 'fa-expand', 'fa-compress', 'fa-random',
|
||||
// Misc
|
||||
'fa-tachometer-alt', 'fa-chart-line', 'fa-chart-bar', 'fa-chart-pie', 'fa-chart-area',
|
||||
'fa-clock', 'fa-calendar', 'fa-tag', 'fa-tags', 'fa-bookmark', 'fa-star', 'fa-heart',
|
||||
'fa-check', 'fa-times', 'fa-plus', 'fa-minus', 'fa-info-circle', 'fa-question-circle',
|
||||
'fa-dot-circle', 'fa-circle', 'fa-square', 'fa-adjust'
|
||||
],
|
||||
|
||||
customIcons: [],
|
||||
currentInput: null,
|
||||
currentTab: 'fontawesome',
|
||||
|
||||
init: function() {
|
||||
this.createModal();
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
createModal: function() {
|
||||
if ($('#kundenkarte-icon-picker-modal').length) return;
|
||||
|
||||
var self = this;
|
||||
var html = '<div id="kundenkarte-icon-picker-modal" class="kundenkarte-modal">';
|
||||
html += '<div class="kundenkarte-modal-content" style="max-width:700px;">';
|
||||
html += '<div class="kundenkarte-modal-header">';
|
||||
html += '<h3>Icon auswählen</h3>';
|
||||
html += '<span class="kundenkarte-modal-close">×</span>';
|
||||
html += '</div>';
|
||||
html += '<div class="kundenkarte-modal-body">';
|
||||
|
||||
// Tabs
|
||||
html += '<div class="kundenkarte-icon-tabs" style="display:flex;gap:5px;margin-bottom:15px;border-bottom:2px solid #e0e0e0;padding-bottom:10px;">';
|
||||
html += '<button type="button" class="button kundenkarte-icon-tab active" data-tab="fontawesome"><i class="fa fa-font-awesome"></i> Font Awesome</button>';
|
||||
html += '<button type="button" class="button kundenkarte-icon-tab" data-tab="custom"><i class="fa fa-upload"></i> Eigene Icons</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Font Awesome Tab Content
|
||||
html += '<div class="kundenkarte-icon-tab-content" data-tab="fontawesome">';
|
||||
html += '<input type="text" id="kundenkarte-icon-search" class="flat" placeholder="Suchen..." style="width:100%;margin-bottom:10px;">';
|
||||
html += '<div class="kundenkarte-icon-grid" id="kundenkarte-fa-icons">';
|
||||
for (var i = 0; i < this.icons.length; i++) {
|
||||
html += '<div class="kundenkarte-icon-item" data-icon="' + this.icons[i] + '" data-type="fa" title="' + this.icons[i] + '">';
|
||||
html += '<i class="fa ' + this.icons[i] + '"></i>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
// Custom Icons Tab Content
|
||||
html += '<div class="kundenkarte-icon-tab-content" data-tab="custom" style="display:none;">';
|
||||
// Upload area
|
||||
html += '<div class="kundenkarte-icon-upload-area" style="border:2px dashed #ccc;border-radius:8px;padding:20px;text-align:center;margin-bottom:15px;background:#f9f9f9;">';
|
||||
html += '<i class="fa fa-cloud-upload" style="font-size:32px;color:#888;margin-bottom:10px;display:block;"></i>';
|
||||
html += '<p style="margin:0 0 10px 0;">Icon hochladen (PNG, JPG, SVG, max 500KB)</p>';
|
||||
html += '<input type="file" id="kundenkarte-icon-upload" accept=".png,.jpg,.jpeg,.gif,.svg,.webp" style="display:none;">';
|
||||
html += '<button type="button" class="button" id="kundenkarte-icon-upload-btn"><i class="fa fa-plus"></i> Datei auswählen</button>';
|
||||
html += '</div>';
|
||||
// Custom icons grid
|
||||
html += '<div class="kundenkarte-icon-grid" id="kundenkarte-custom-icons">';
|
||||
html += '<p class="opacitymedium" style="grid-column:1/-1;text-align:center;">Lade eigene Icons...</p>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
$('body').append(html);
|
||||
},
|
||||
|
||||
bindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
// Open picker
|
||||
$(document).on('click', '.kundenkarte-icon-picker-btn', function(e) {
|
||||
e.preventDefault();
|
||||
var inputName = $(this).data('input');
|
||||
self.currentInput = $('input[name="' + inputName + '"]');
|
||||
self.open();
|
||||
});
|
||||
|
||||
// Close modal
|
||||
$(document).on('click', '.kundenkarte-modal-close, #kundenkarte-icon-picker-modal', function(e) {
|
||||
if (e.target === this || $(e.target).hasClass('kundenkarte-modal-close')) {
|
||||
self.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Tab switching
|
||||
$(document).on('click', '.kundenkarte-icon-tab', function() {
|
||||
var tab = $(this).data('tab');
|
||||
self.switchTab(tab);
|
||||
});
|
||||
|
||||
// Select FA icon
|
||||
$(document).on('click', '.kundenkarte-icon-item[data-type="fa"]', function() {
|
||||
var icon = $(this).data('icon');
|
||||
self.selectIcon(icon, 'fa');
|
||||
});
|
||||
|
||||
// Select custom icon
|
||||
$(document).on('click', '.kundenkarte-icon-item[data-type="custom"]', function() {
|
||||
var iconUrl = $(this).data('icon');
|
||||
self.selectIcon(iconUrl, 'custom');
|
||||
});
|
||||
|
||||
// Search filter (FA only)
|
||||
$(document).on('input', '#kundenkarte-icon-search', function() {
|
||||
var search = $(this).val().toLowerCase();
|
||||
$('#kundenkarte-fa-icons .kundenkarte-icon-item').each(function() {
|
||||
var icon = $(this).data('icon').toLowerCase();
|
||||
$(this).toggle(icon.indexOf(search) > -1);
|
||||
});
|
||||
});
|
||||
|
||||
// Upload button click
|
||||
$(document).on('click', '#kundenkarte-icon-upload-btn', function() {
|
||||
$('#kundenkarte-icon-upload').click();
|
||||
});
|
||||
|
||||
// File selected
|
||||
$(document).on('change', '#kundenkarte-icon-upload', function() {
|
||||
var file = this.files[0];
|
||||
if (file) {
|
||||
self.uploadIcon(file);
|
||||
}
|
||||
});
|
||||
|
||||
// Delete custom icon
|
||||
$(document).on('click', '.kundenkarte-icon-delete', function(e) {
|
||||
e.stopPropagation();
|
||||
var filename = $(this).data('filename');
|
||||
self.showDeleteConfirm(filename);
|
||||
});
|
||||
|
||||
// ESC key to close
|
||||
$(document).on('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
self.close();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
switchTab: function(tab) {
|
||||
this.currentTab = tab;
|
||||
$('.kundenkarte-icon-tab').removeClass('active');
|
||||
$('.kundenkarte-icon-tab[data-tab="' + tab + '"]').addClass('active');
|
||||
$('.kundenkarte-icon-tab-content').hide();
|
||||
$('.kundenkarte-icon-tab-content[data-tab="' + tab + '"]').show();
|
||||
|
||||
if (tab === 'custom') {
|
||||
this.loadCustomIcons();
|
||||
}
|
||||
},
|
||||
|
||||
loadCustomIcons: function() {
|
||||
var self = this;
|
||||
var $grid = $('#kundenkarte-custom-icons');
|
||||
|
||||
$.ajax({
|
||||
url: baseUrl + '/custom/kundenkarte/ajax/icon_upload.php',
|
||||
data: { action: 'list' },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
$grid.empty();
|
||||
|
||||
if (response.icons && response.icons.length > 0) {
|
||||
self.customIcons = response.icons;
|
||||
for (var i = 0; i < response.icons.length; i++) {
|
||||
var icon = response.icons[i];
|
||||
var html = '<div class="kundenkarte-icon-item kundenkarte-custom-icon-item" data-icon="' + icon.url + '" data-type="custom" title="' + icon.name + '" style="position:relative;">';
|
||||
html += '<img src="' + icon.url + '" alt="' + icon.name + '" style="max-width:32px;max-height:32px;">';
|
||||
html += '<span class="kundenkarte-icon-delete" data-filename="' + icon.filename + '" title="Löschen" style="position:absolute;top:-5px;right:-5px;background:#c00;color:#fff;border-radius:50%;width:16px;height:16px;font-size:10px;line-height:16px;text-align:center;cursor:pointer;display:none;">×</span>';
|
||||
html += '</div>';
|
||||
$grid.append(html);
|
||||
}
|
||||
} else {
|
||||
$grid.html('<p class="opacitymedium" style="grid-column:1/-1;text-align:center;">Noch keine eigenen Icons hochgeladen.</p>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$grid.html('<p class="warning" style="grid-column:1/-1;text-align:center;">Fehler beim Laden der Icons.</p>');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
uploadIcon: function(file) {
|
||||
var self = this;
|
||||
var formData = new FormData();
|
||||
formData.append('action', 'upload');
|
||||
formData.append('icon', file);
|
||||
|
||||
$.ajax({
|
||||
url: baseUrl + '/custom/kundenkarte/ajax/icon_upload.php',
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
self.loadCustomIcons();
|
||||
// Auto-select uploaded icon
|
||||
setTimeout(function() {
|
||||
self.selectIcon(response.icon.url, 'custom');
|
||||
}, 500);
|
||||
} else {
|
||||
alert('Fehler: ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
var msg = 'Upload fehlgeschlagen';
|
||||
try {
|
||||
var resp = JSON.parse(xhr.responseText);
|
||||
msg = resp.error || msg;
|
||||
} catch(e) {}
|
||||
alert(msg);
|
||||
}
|
||||
});
|
||||
|
||||
// Reset input
|
||||
$('#kundenkarte-icon-upload').val('');
|
||||
},
|
||||
|
||||
deleteIcon: function(filename) {
|
||||
var self = this;
|
||||
|
||||
$.ajax({
|
||||
url: baseUrl + '/custom/kundenkarte/ajax/icon_upload.php',
|
||||
method: 'POST',
|
||||
data: { action: 'delete', filename: filename },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
self.loadCustomIcons();
|
||||
} else {
|
||||
alert('Fehler: ' + (response.error || 'Löschen fehlgeschlagen'));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showDeleteConfirm: function(filename) {
|
||||
var self = this;
|
||||
|
||||
// Remove any existing confirm dialog
|
||||
$('#kundenkarte-delete-confirm').remove();
|
||||
|
||||
// Create Dolibarr-style confirmation dialog
|
||||
var html = '<div id="kundenkarte-delete-confirm" class="kundenkarte-confirm-overlay" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:10001;display:flex;align-items:center;justify-content:center;">';
|
||||
html += '<div class="kundenkarte-confirm-box" style="background:#fff;border-radius:6px;box-shadow:0 4px 20px rgba(0,0,0,0.3);max-width:400px;width:90%;">';
|
||||
|
||||
// Header (Dolibarr style)
|
||||
html += '<div class="kundenkarte-confirm-header" style="background:linear-gradient(to bottom,#f8f8f8,#e8e8e8);border-bottom:1px solid #ccc;padding:12px 15px;border-radius:6px 6px 0 0;">';
|
||||
html += '<span style="font-weight:bold;font-size:14px;"><i class="fa fa-exclamation-triangle" style="color:#f0ad4e;margin-right:8px;"></i>Bestätigung</span>';
|
||||
html += '</div>';
|
||||
|
||||
// Body
|
||||
html += '<div class="kundenkarte-confirm-body" style="padding:20px;text-align:center;">';
|
||||
html += '<p style="margin:0 0 5px 0;font-size:14px;">Möchten Sie dieses Icon wirklich löschen?</p>';
|
||||
html += '<p style="margin:0;color:#666;font-size:12px;"><code>' + this.escapeHtml(filename) + '</code></p>';
|
||||
html += '</div>';
|
||||
|
||||
// Footer with buttons (Dolibarr style)
|
||||
html += '<div class="kundenkarte-confirm-footer" style="background:#f5f5f5;border-top:1px solid #ddd;padding:12px 15px;text-align:center;border-radius:0 0 6px 6px;">';
|
||||
html += '<button type="button" class="button" id="kundenkarte-confirm-yes" style="background:#c9302c;color:#fff;border:1px solid #ac2925;padding:6px 20px;margin-right:10px;cursor:pointer;"><i class="fa fa-check"></i> Ja, löschen</button>';
|
||||
html += '<button type="button" class="button" id="kundenkarte-confirm-no" style="padding:6px 20px;cursor:pointer;"><i class="fa fa-times"></i> Abbrechen</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
$('body').append(html);
|
||||
|
||||
// Bind events
|
||||
$('#kundenkarte-confirm-yes').on('click', function() {
|
||||
$('#kundenkarte-delete-confirm').remove();
|
||||
self.deleteIcon(filename);
|
||||
});
|
||||
|
||||
$('#kundenkarte-confirm-no, #kundenkarte-delete-confirm').on('click', function(e) {
|
||||
if (e.target === this) {
|
||||
$('#kundenkarte-delete-confirm').remove();
|
||||
}
|
||||
});
|
||||
|
||||
// ESC to close
|
||||
$(document).one('keydown.confirmDialog', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
$('#kundenkarte-delete-confirm').remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
escapeHtml: function(text) {
|
||||
if (!text) return '';
|
||||
var div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
selectIcon: function(icon, type) {
|
||||
if (this.currentInput) {
|
||||
// For custom icons, store the URL with a prefix to distinguish
|
||||
var value = (type === 'custom') ? 'img:' + icon : icon;
|
||||
this.currentInput.val(value);
|
||||
|
||||
// Update preview
|
||||
var $wrapper = this.currentInput.closest('.kundenkarte-icon-picker-wrapper');
|
||||
var $preview = $wrapper.find('.kundenkarte-icon-preview');
|
||||
if ($preview.length) {
|
||||
if (type === 'custom') {
|
||||
$preview.html('<img src="' + icon + '" style="max-width:20px;max-height:20px;">');
|
||||
} else {
|
||||
$preview.html('<i class="fa ' + icon + '"></i>');
|
||||
}
|
||||
}
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
|
||||
open: function() {
|
||||
$('#kundenkarte-icon-search').val('');
|
||||
$('#kundenkarte-fa-icons .kundenkarte-icon-item').show();
|
||||
this.switchTab('fontawesome');
|
||||
$('#kundenkarte-icon-picker-modal').addClass('visible');
|
||||
},
|
||||
|
||||
close: function() {
|
||||
$('#kundenkarte-icon-picker-modal').removeClass('visible');
|
||||
this.currentInput = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
$(document).ready(function() {
|
||||
KundenKarte.Tree.init();
|
||||
KundenKarte.Favorites.init();
|
||||
KundenKarte.SystemTabs.init();
|
||||
KundenKarte.IconPicker.init();
|
||||
});
|
||||
|
||||
})();
|
||||
259
kundenkarteindex.php
Executable file
259
kundenkarteindex.php
Executable file
|
|
@ -0,0 +1,259 @@
|
|||
<?php
|
||||
/* Copyright (C) 2001-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
|
||||
* Copyright (C) 2004-2015 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||
* Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
|
||||
* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
|
||||
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
|
||||
* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file kundenkarte/kundenkarteindex.php
|
||||
* \ingroup kundenkarte
|
||||
* \brief Home page of kundenkarte top menu
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
|
||||
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
|
||||
}
|
||||
// Try main.inc.php using relative path
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../../main.inc.php")) {
|
||||
$res = @include "../../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var HookManager $hookmanager
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
// Load translation files required by the page
|
||||
$langs->loadLangs(array("kundenkarte@kundenkarte"));
|
||||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
|
||||
$now = dol_now();
|
||||
$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5);
|
||||
|
||||
// Security check - Protection if external user
|
||||
$socid = GETPOSTINT('socid');
|
||||
if (!empty($user->socid) && $user->socid > 0) {
|
||||
$action = '';
|
||||
$socid = $user->socid;
|
||||
}
|
||||
|
||||
// Initialize a technical object to manage hooks. Note that conf->hooks_modules contains array
|
||||
//$hookmanager->initHooks(array($object->element.'index'));
|
||||
|
||||
// Security check (enable the most restrictive one)
|
||||
//if ($user->socid > 0) accessforbidden();
|
||||
//if ($user->socid > 0) $socid = $user->socid;
|
||||
//if (!isModEnabled('kundenkarte')) {
|
||||
// accessforbidden('Module not enabled');
|
||||
//}
|
||||
//if (! $user->hasRight('kundenkarte', 'myobject', 'read')) {
|
||||
// accessforbidden();
|
||||
//}
|
||||
//restrictedArea($user, 'kundenkarte', 0, 'kundenkarte_myobject', 'myobject', '', 'rowid');
|
||||
//if (empty($user->admin)) {
|
||||
// accessforbidden('Must be admin');
|
||||
//}
|
||||
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
// None
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$form = new Form($db);
|
||||
$formfile = new FormFile($db);
|
||||
|
||||
llxHeader("", $langs->trans("KundenKarteArea"), '', '', 0, 0, '', '', '', 'mod-kundenkarte page-index');
|
||||
|
||||
print load_fiche_titre($langs->trans("KundenKarteArea"), '', 'kundenkarte.png@kundenkarte');
|
||||
|
||||
print '<div class="fichecenter"><div class="fichethirdleft">';
|
||||
|
||||
|
||||
/* BEGIN MODULEBUILDER DRAFT MYOBJECT
|
||||
// Draft MyObject
|
||||
if (isModEnabled('kundenkarte') && $user->hasRight('kundenkarte', 'read')) {
|
||||
$langs->load("orders");
|
||||
|
||||
$sql = "SELECT c.rowid, c.ref, c.ref_client, c.total_ht, c.tva as total_tva, c.total_ttc, s.rowid as socid, s.nom as name, s.client, s.canvas";
|
||||
$sql.= ", s.code_client";
|
||||
$sql.= " FROM ".$db->prefix()."commande as c";
|
||||
$sql.= ", ".$db->prefix()."societe as s";
|
||||
$sql.= " WHERE c.fk_soc = s.rowid";
|
||||
$sql.= " AND c.fk_statut = 0";
|
||||
$sql.= " AND c.entity IN (".getEntity('commande').")";
|
||||
if ($socid) $sql.= " AND c.fk_soc = ".((int) $socid);
|
||||
|
||||
$resql = $db->query($sql);
|
||||
if ($resql)
|
||||
{
|
||||
$total = 0;
|
||||
$num = $db->num_rows($resql);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th colspan="3">'.$langs->trans("DraftMyObjects").($num?'<span class="badge marginleftonlyshort">'.$num.'</span>':'').'</th></tr>';
|
||||
|
||||
$var = true;
|
||||
if ($num > 0)
|
||||
{
|
||||
$i = 0;
|
||||
while ($i < $num)
|
||||
{
|
||||
|
||||
$obj = $db->fetch_object($resql);
|
||||
print '<tr class="oddeven"><td class="nowrap">';
|
||||
|
||||
$myobjectstatic->id=$obj->rowid;
|
||||
$myobjectstatic->ref=$obj->ref;
|
||||
$myobjectstatic->ref_client=$obj->ref_client;
|
||||
$myobjectstatic->total_ht = $obj->total_ht;
|
||||
$myobjectstatic->total_tva = $obj->total_tva;
|
||||
$myobjectstatic->total_ttc = $obj->total_ttc;
|
||||
|
||||
print $myobjectstatic->getNomUrl(1);
|
||||
print '</td>';
|
||||
print '<td class="nowrap">';
|
||||
print '</td>';
|
||||
print '<td class="right" class="nowrap">'.price($obj->total_ttc).'</td></tr>';
|
||||
$i++;
|
||||
$total += $obj->total_ttc;
|
||||
}
|
||||
if ($total>0)
|
||||
{
|
||||
|
||||
print '<tr class="liste_total"><td>'.$langs->trans("Total").'</td><td colspan="2" class="right">'.price($total)."</td></tr>";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
print '<tr class="oddeven"><td colspan="3" class="opacitymedium">'.$langs->trans("NoOrder").'</td></tr>';
|
||||
}
|
||||
print "</table><br>";
|
||||
|
||||
$db->free($resql);
|
||||
}
|
||||
else
|
||||
{
|
||||
dol_print_error($db);
|
||||
}
|
||||
}
|
||||
END MODULEBUILDER DRAFT MYOBJECT */
|
||||
|
||||
|
||||
print '</div><div class="fichetwothirdright">';
|
||||
|
||||
|
||||
/* BEGIN MODULEBUILDER LASTMODIFIED MYOBJECT
|
||||
// Last modified myobject
|
||||
if (isModEnabled('kundenkarte') && $user->hasRight('kundenkarte', 'read')) {
|
||||
$sql = "SELECT s.rowid, s.ref, s.label, s.date_creation, s.tms";
|
||||
$sql.= " FROM ".$db->prefix()."kundenkarte_myobject as s";
|
||||
$sql.= " WHERE s.entity IN (".getEntity($myobjectstatic->element).")";
|
||||
//if ($socid) $sql.= " AND s.rowid = $socid";
|
||||
$sql .= " ORDER BY s.tms DESC";
|
||||
$sql .= $db->plimit($max, 0);
|
||||
|
||||
$resql = $db->query($sql);
|
||||
if ($resql)
|
||||
{
|
||||
$num = $db->num_rows($resql);
|
||||
$i = 0;
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th colspan="2">';
|
||||
print $langs->trans("BoxTitleLatestModifiedMyObjects", $max);
|
||||
print '</th>';
|
||||
print '<th class="right">'.$langs->trans("DateModificationShort").'</th>';
|
||||
print '</tr>';
|
||||
if ($num)
|
||||
{
|
||||
while ($i < $num)
|
||||
{
|
||||
$objp = $db->fetch_object($resql);
|
||||
|
||||
$myobjectstatic->id=$objp->rowid;
|
||||
$myobjectstatic->ref=$objp->ref;
|
||||
$myobjectstatic->label=$objp->label;
|
||||
$myobjectstatic->status = $objp->status;
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
print '<td class="nowrap">'.$myobjectstatic->getNomUrl(1).'</td>';
|
||||
print '<td class="right nowrap">';
|
||||
print "</td>";
|
||||
print '<td class="right nowrap">'.dol_print_date($db->jdate($objp->tms), 'day')."</td>";
|
||||
print '</tr>';
|
||||
$i++;
|
||||
}
|
||||
|
||||
$db->free($resql);
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="3" class="opacitymedium">'.$langs->trans("None").'</td></tr>';
|
||||
}
|
||||
print "</table><br>";
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
print '</div></div>';
|
||||
|
||||
// End of page
|
||||
llxFooter();
|
||||
$db->close();
|
||||
204
langs/de_DE/kundenkarte.lang
Executable file
204
langs/de_DE/kundenkarte.lang
Executable file
|
|
@ -0,0 +1,204 @@
|
|||
# Copyright (C) 2026 Alles Watt lauft
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# Module
|
||||
ModuleKundenKarteName = KundenKarte
|
||||
ModuleKundenKarteDesc = Kundenkarte mit Favoriten und technischen Anlagen
|
||||
KundenKarteSetupPage = Hier koennen Sie die Einstellungen fuer das Modul KundenKarte konfigurieren.
|
||||
|
||||
# Tabs
|
||||
FavoriteProducts = Favoriten
|
||||
TechnicalInstallations = Anlagen
|
||||
|
||||
# Favorite Products
|
||||
FavoriteProductsList = Favorisierte Produkte
|
||||
AddFavoriteProduct = Produkt hinzufuegen
|
||||
NoFavoriteProducts = Keine favorisierten Produkte vorhanden
|
||||
GenerateOrder = Bestellung generieren
|
||||
SelectProducts = Produkte auswaehlen
|
||||
DefaultQuantity = Standard-Menge
|
||||
OrderGenerated = Bestellung %s wurde erstellt
|
||||
ConfirmGenerateOrder = Moechten Sie eine Bestellung aus den ausgewaehlten Produkten erstellen?
|
||||
OrderGeneratedFromFavorites = Generiert aus Favoriten-Produkten
|
||||
NoProductsSelected = Keine Produkte ausgewaehlt
|
||||
SelectAll = Alle auswaehlen
|
||||
Up = Nach oben
|
||||
Down = Nach unten
|
||||
Qty = Menge
|
||||
UnitPriceHT = Preis (netto)
|
||||
Total = Gesamt
|
||||
|
||||
# Technical Installations
|
||||
AnlagenSystems = Anlagen-Systeme
|
||||
AnlagenTypes = Element-Typen
|
||||
AnlagenTypeFields = Typ-Felder
|
||||
AddSystem = System hinzufuegen
|
||||
AddType = Typ hinzufuegen
|
||||
AddField = Feld hinzufuegen
|
||||
AddElement = Element hinzufuegen
|
||||
EditElement = Element bearbeiten
|
||||
DeleteElement = Element loeschen
|
||||
NoInstallations = Keine Installationen vorhanden
|
||||
SelectSystem = System auswaehlen
|
||||
SelectType = Typ auswaehlen
|
||||
SelectParent = Uebergeordnetes Element
|
||||
Root = Stamm
|
||||
|
||||
# Customer System Management
|
||||
SystemAdded = System wurde hinzugefuegt
|
||||
SystemRemoved = System wurde entfernt
|
||||
RemoveSystem = System entfernen
|
||||
ConfirmRemoveSystem = Moechten Sie dieses System wirklich vom Kunden entfernen?
|
||||
NoSystemsConfigured = Keine Systeme konfiguriert
|
||||
ClickAddSystemToStart = Klicken Sie auf "System hinzufuegen" um Systeme fuer diesen Kunden hinzuzufuegen
|
||||
ContactAdminToAddSystems = Bitte erstellen Sie zuerst Systeme im Admin-Bereich
|
||||
SelectSystemToAdd = System zum Hinzufuegen auswaehlen
|
||||
ErrorSystemHasElements = System kann nicht entfernt werden, da noch Elemente vorhanden sind
|
||||
NoTypesDefinedForSystem = Keine Typen fuer dieses System definiert
|
||||
AvailableSystems = Verfuegbare Systeme
|
||||
NoMoreSystemsAvailable = Alle verfuegbaren Systeme wurden bereits hinzugefuegt
|
||||
|
||||
# System Categories
|
||||
SystemStrom = Strom
|
||||
SystemInternet = Internet/Netzwerk
|
||||
SystemKabel = Kabelfernsehen
|
||||
SystemSat = Satelliten
|
||||
|
||||
# Element Types
|
||||
TypeZaehlerschrank = Zaehlerschrank
|
||||
TypeUnterverteilung = Unterverteilung
|
||||
TypeWallbox = Wallbox
|
||||
TypePVSpeicher = PV-Speicher
|
||||
TypeWechselrichter = Wechselrichter
|
||||
TypeWaermepumpe = Waermepumpe
|
||||
TypeRouter = Router
|
||||
TypeSwitch = Switch
|
||||
TypeAccessPoint = Access Point
|
||||
TypeNetzwerkschrank = Netzwerkschrank
|
||||
|
||||
# Fields
|
||||
FieldSize = Groesse
|
||||
FieldFieldCount = Feldanzahl
|
||||
FieldManufacturer = Hersteller
|
||||
FieldModel = Modell/Typ
|
||||
FieldSerialNumber = Seriennummer
|
||||
FieldPowerRating = Leistung
|
||||
FieldLocation = Standort
|
||||
FieldInstallationDate = Installationsdatum
|
||||
FieldWarrantyUntil = Garantie bis
|
||||
FieldNotes = Hinweise
|
||||
|
||||
# Tree
|
||||
CanHaveChildren = Kann Unterelemente haben
|
||||
CanBeNested = Kann verschachtelt werden
|
||||
SameTypeUnderItself = Gleicher Typ unter sich selbst erlaubt
|
||||
AllowedParentTypes = Erlaubte Eltern-Typen
|
||||
AllowedParentTypesHelp = Leer = alle Typen erlaubt
|
||||
CommaSeperatedRefs = Komma-getrennte Referenzen
|
||||
ShowInTree = Im Baum anzeigen
|
||||
ShowInHover = Im Hover anzeigen
|
||||
IsRequired = Pflichtfeld
|
||||
ExpandAll = Alles ausklappen
|
||||
CollapseAll = Alles einklappen
|
||||
|
||||
# Files
|
||||
UploadFiles = Dateien hochladen
|
||||
AttachedFiles = Angehaengte Dateien
|
||||
NoFiles = Keine Dateien vorhanden
|
||||
SetAsCover = Als Vorschaubild setzen
|
||||
ConfirmDeleteFile = Moechten Sie diese Datei wirklich loeschen?
|
||||
FileDeleted = Datei wurde geloescht
|
||||
FileUploaded = Datei wurde hochgeladen
|
||||
|
||||
# Admin
|
||||
Settings = Einstellungen
|
||||
About = Ueber
|
||||
ManageSystems = Systeme verwalten
|
||||
ManageTypes = Typen verwalten
|
||||
ManageFields = Felder verwalten
|
||||
SystemCode = System-Code
|
||||
SystemLabel = Bezeichnung
|
||||
SystemPicto = Icon
|
||||
SystemColor = Farbe
|
||||
TypeRef = Referenz
|
||||
TypeLabel = Bezeichnung
|
||||
TypeShortLabel = Kurzbezeichnung
|
||||
FieldCode = Feld-Code
|
||||
FieldLabel = Feld-Bezeichnung
|
||||
FieldType = Feld-Typ
|
||||
FieldOptions = Optionen (JSON)
|
||||
FontAwesomeIcon = FontAwesome Icon
|
||||
SelectIcon = Icon auswaehlen
|
||||
Position = Position
|
||||
Status = Status
|
||||
Actions = Aktionen
|
||||
System = System
|
||||
Description = Beschreibung
|
||||
FilterBySystem = Nach System filtern
|
||||
All = Alle
|
||||
NoRecords = Keine Eintraege vorhanden
|
||||
NoFieldsDefined = Keine Felder definiert
|
||||
EditFieldsInDatabase = Felder koennen in der Datenbank oder ueber SQL bearbeitet werden
|
||||
FieldOptionsHelp = Bei Typ "Select": Optionen mit | trennen (z.B. Option1|Option2|Option3)
|
||||
|
||||
# Field Types
|
||||
Text = Text
|
||||
Number = Zahl
|
||||
Date = Datum
|
||||
Textarea = Textfeld (mehrzeilig)
|
||||
Checkbox = Checkbox
|
||||
|
||||
# Confirmations
|
||||
ConfirmDeleteSystem = Moechten Sie dieses System wirklich loeschen?
|
||||
ConfirmDeleteType = Moechten Sie diesen Typ wirklich loeschen?
|
||||
ConfirmDeleteElement = Moechten Sie dieses Element wirklich loeschen?
|
||||
ConfirmDeleteField = Moechten Sie dieses Feld wirklich loeschen?
|
||||
|
||||
# Errors
|
||||
ErrorSystemInUse = System kann nicht geloescht werden, da es noch verwendet wird
|
||||
ErrorTypeInUse = Typ kann nicht geloescht werden, da er noch verwendet wird
|
||||
ErrorParentNotAllowed = Dieses Element kann nicht unter dem ausgewaehlten Elternelement platziert werden
|
||||
ErrorFieldRequired = Pflichtfeld nicht ausgefuellt
|
||||
|
||||
# Setup Page
|
||||
KundenKarteSetup = KundenKarte Einstellungen
|
||||
Parameter = Parameter
|
||||
Value = Wert
|
||||
ShowFavoritesTab = Tab "Favoriten" anzeigen
|
||||
ShowAnlagenTab = Tab "Anlagen" anzeigen
|
||||
DefaultOrderType = Standard-Bestelltyp fuer Favoriten
|
||||
OrderTypeOrder = Bestellung (Commande)
|
||||
OrderTypeProposal = Angebot (Propal)
|
||||
ConfigurationHelp = Konfigurationshinweise
|
||||
ConfigHelpSystems = Systeme verwalten: Gehen Sie zum Tab "Anlagen-Systeme" um eigene System-Kategorien anzulegen
|
||||
ConfigHelpTypes = Element-Typen verwalten: Gehen Sie zum Tab "Element-Typen" um Geraetetypen und Felder zu definieren
|
||||
SetupSaved = Einstellungen gespeichert
|
||||
|
||||
# Standard Dolibarr
|
||||
Save = Speichern
|
||||
Cancel = Abbrechen
|
||||
Delete = Loeschen
|
||||
Enabled = Aktiviert
|
||||
Disabled = Deaktiviert
|
||||
RecordSaved = Eintrag gespeichert
|
||||
RecordDeleted = Eintrag geloescht
|
||||
BackToModuleList = Zurueck zur Modulliste
|
||||
Error = Fehler
|
||||
Select = Auswaehlen
|
||||
Add = Hinzufuegen
|
||||
AddChild = Unterelement hinzufuegen
|
||||
Label = Bezeichnung
|
||||
Type = Typ
|
||||
Upload = Hochladen
|
||||
View = Ansehen
|
||||
Back = Zurueck
|
||||
Modify = Bearbeiten
|
||||
Edit = Bearbeiten
|
||||
Close = Schliessen
|
||||
Confirm = Bestaetigen
|
||||
Yes = Ja
|
||||
No = Nein
|
||||
187
langs/en_US/kundenkarte.lang
Executable file
187
langs/en_US/kundenkarte.lang
Executable file
|
|
@ -0,0 +1,187 @@
|
|||
# Copyright (C) 2026 Alles Watt lauft
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# Module
|
||||
ModuleKundenKarteName = KundenKarte
|
||||
ModuleKundenKarteDesc = Customer card with favorites and technical installations
|
||||
KundenKarteSetupPage = Configure the settings for the KundenKarte module here.
|
||||
|
||||
# Tabs
|
||||
FavoriteProducts = Favorites
|
||||
TechnicalInstallations = Installations
|
||||
|
||||
# Favorite Products
|
||||
FavoriteProductsList = Favorite Products
|
||||
AddFavoriteProduct = Add product
|
||||
NoFavoriteProducts = No favorite products
|
||||
GenerateOrder = Generate Order
|
||||
SelectProducts = Select products
|
||||
DefaultQuantity = Default quantity
|
||||
OrderGenerated = Order %s has been created
|
||||
ConfirmGenerateOrder = Do you want to create an order from the selected products?
|
||||
OrderGeneratedFromFavorites = Generated from favorite products
|
||||
NoProductsSelected = No products selected
|
||||
SelectAll = Select all
|
||||
Up = Up
|
||||
Down = Down
|
||||
Qty = Qty
|
||||
UnitPriceHT = Price (excl. tax)
|
||||
Total = Total
|
||||
|
||||
# Technical Installations
|
||||
AnlagenSystems = Installation Systems
|
||||
AnlagenTypes = Element Types
|
||||
AnlagenTypeFields = Type Fields
|
||||
AddSystem = Add system
|
||||
AddType = Add type
|
||||
AddField = Add field
|
||||
AddElement = Add element
|
||||
EditElement = Edit element
|
||||
DeleteElement = Delete element
|
||||
NoInstallations = No installations
|
||||
SelectSystem = Select system
|
||||
SelectType = Select type
|
||||
SelectParent = Parent element
|
||||
Root = Root
|
||||
|
||||
# Customer System Management
|
||||
SystemAdded = System has been added
|
||||
SystemRemoved = System has been removed
|
||||
RemoveSystem = Remove system
|
||||
ConfirmRemoveSystem = Are you sure you want to remove this system from this customer?
|
||||
NoSystemsConfigured = No systems configured
|
||||
ClickAddSystemToStart = Click "Add system" to add systems for this customer
|
||||
ContactAdminToAddSystems = Please create systems in the admin area first
|
||||
SelectSystemToAdd = Select system to add
|
||||
ErrorSystemHasElements = System cannot be removed because it still has elements
|
||||
NoTypesDefinedForSystem = No types defined for this system
|
||||
AvailableSystems = Available systems
|
||||
NoMoreSystemsAvailable = All available systems have already been added
|
||||
|
||||
# Fields
|
||||
FieldSize = Size
|
||||
FieldFieldCount = Field count
|
||||
FieldManufacturer = Manufacturer
|
||||
FieldModel = Model/Type
|
||||
FieldSerialNumber = Serial number
|
||||
FieldPowerRating = Power rating
|
||||
FieldLocation = Location
|
||||
FieldInstallationDate = Installation date
|
||||
FieldWarrantyUntil = Warranty until
|
||||
FieldNotes = Notes
|
||||
|
||||
# Tree
|
||||
CanHaveChildren = Can have children
|
||||
CanBeNested = Can be nested
|
||||
SameTypeUnderItself = Same type under itself allowed
|
||||
AllowedParentTypes = Allowed parent types
|
||||
AllowedParentTypesHelp = Empty = all types allowed
|
||||
CommaSeperatedRefs = Comma separated refs
|
||||
ShowInTree = Show in tree
|
||||
ShowInHover = Show on hover
|
||||
IsRequired = Required
|
||||
ExpandAll = Expand all
|
||||
CollapseAll = Collapse all
|
||||
|
||||
# Files
|
||||
UploadFiles = Upload files
|
||||
AttachedFiles = Attached files
|
||||
NoFiles = No files
|
||||
SetAsCover = Set as cover
|
||||
ConfirmDeleteFile = Are you sure you want to delete this file?
|
||||
FileDeleted = File has been deleted
|
||||
FileUploaded = File has been uploaded
|
||||
|
||||
# Admin
|
||||
Settings = Settings
|
||||
About = About
|
||||
ManageSystems = Manage systems
|
||||
ManageTypes = Manage types
|
||||
ManageFields = Manage fields
|
||||
SystemCode = System code
|
||||
SystemLabel = Label
|
||||
SystemPicto = Icon
|
||||
SystemColor = Color
|
||||
TypeRef = Reference
|
||||
TypeLabel = Label
|
||||
TypeShortLabel = Short label
|
||||
FieldCode = Field code
|
||||
FieldLabel = Field label
|
||||
FieldType = Field type
|
||||
FieldOptions = Options (JSON)
|
||||
FontAwesomeIcon = FontAwesome icon
|
||||
SelectIcon = Select icon
|
||||
Position = Position
|
||||
Status = Status
|
||||
Actions = Actions
|
||||
System = System
|
||||
Description = Description
|
||||
FilterBySystem = Filter by system
|
||||
All = All
|
||||
NoRecords = No records
|
||||
NoFieldsDefined = No fields defined
|
||||
EditFieldsInDatabase = Fields can be edited in database or via SQL
|
||||
FieldOptionsHelp = For type "Select": separate options with | (e.g. Option1|Option2|Option3)
|
||||
|
||||
# Field Types
|
||||
Text = Text
|
||||
Number = Number
|
||||
Date = Date
|
||||
Textarea = Textarea (multiline)
|
||||
Checkbox = Checkbox
|
||||
|
||||
# Setup Page
|
||||
KundenKarteSetup = KundenKarte Settings
|
||||
Parameter = Parameter
|
||||
Value = Value
|
||||
ShowFavoritesTab = Show "Favorites" tab
|
||||
ShowAnlagenTab = Show "Installations" tab
|
||||
DefaultOrderType = Default order type for favorites
|
||||
OrderTypeOrder = Order (Commande)
|
||||
OrderTypeProposal = Proposal (Propal)
|
||||
ConfigurationHelp = Configuration tips
|
||||
ConfigHelpSystems = Manage systems: Go to the "Installation Systems" tab to create custom system categories
|
||||
ConfigHelpTypes = Manage element types: Go to the "Element Types" tab to define device types and fields
|
||||
SetupSaved = Settings saved
|
||||
|
||||
# Confirmations
|
||||
ConfirmDeleteSystem = Are you sure you want to delete this system?
|
||||
ConfirmDeleteType = Are you sure you want to delete this type?
|
||||
ConfirmDeleteElement = Are you sure you want to delete this element?
|
||||
ConfirmDeleteField = Are you sure you want to delete this field?
|
||||
|
||||
# Errors
|
||||
ErrorSystemInUse = System cannot be deleted because it is still in use
|
||||
ErrorTypeInUse = Type cannot be deleted because it is still in use
|
||||
ErrorParentNotAllowed = This element cannot be placed under the selected parent
|
||||
ErrorCannotDeleteSystemType = System types cannot be deleted
|
||||
ErrorFieldRequired = Required field not filled
|
||||
|
||||
# Standard Dolibarr
|
||||
Save = Save
|
||||
Cancel = Cancel
|
||||
Delete = Delete
|
||||
Enabled = Enabled
|
||||
Disabled = Disabled
|
||||
RecordSaved = Record saved
|
||||
RecordDeleted = Record deleted
|
||||
BackToModuleList = Back to module list
|
||||
Error = Error
|
||||
Select = Select
|
||||
Add = Add
|
||||
AddChild = Add child element
|
||||
Label = Label
|
||||
Type = Type
|
||||
Upload = Upload
|
||||
View = View
|
||||
Back = Back
|
||||
Modify = Modify
|
||||
Edit = Edit
|
||||
Close = Close
|
||||
Confirm = Confirm
|
||||
Yes = Yes
|
||||
No = No
|
||||
120
lib/kundenkarte.lib.php
Executable file
120
lib/kundenkarte.lib.php
Executable file
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file kundenkarte/lib/kundenkarte.lib.php
|
||||
* \ingroup kundenkarte
|
||||
* \brief Library files with common functions for KundenKarte
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prepare admin pages header
|
||||
*
|
||||
* @return array<array{string,string,string}>
|
||||
*/
|
||||
function kundenkarteAdminPrepareHead()
|
||||
{
|
||||
global $langs, $conf;
|
||||
|
||||
// global $db;
|
||||
// $extrafields = new ExtraFields($db);
|
||||
// $extrafields->fetch_name_optionals_label('myobject');
|
||||
|
||||
$langs->load("kundenkarte@kundenkarte");
|
||||
|
||||
$h = 0;
|
||||
$head = array();
|
||||
|
||||
$head[$h][0] = dol_buildpath("/kundenkarte/admin/setup.php", 1);
|
||||
$head[$h][1] = $langs->trans("Settings");
|
||||
$head[$h][2] = 'settings';
|
||||
$h++;
|
||||
|
||||
$head[$h][0] = dol_buildpath("/kundenkarte/admin/anlage_systems.php", 1);
|
||||
$head[$h][1] = $langs->trans("AnlagenSystems");
|
||||
$head[$h][2] = 'systems';
|
||||
$h++;
|
||||
|
||||
$head[$h][0] = dol_buildpath("/kundenkarte/admin/anlage_types.php", 1);
|
||||
$head[$h][1] = $langs->trans("AnlagenTypes");
|
||||
$head[$h][2] = 'types';
|
||||
$h++;
|
||||
|
||||
/*
|
||||
$head[$h][0] = dol_buildpath("/kundenkarte/admin/myobject_extrafields.php", 1);
|
||||
$head[$h][1] = $langs->trans("ExtraFields");
|
||||
$nbExtrafields = (isset($extrafields->attributes['myobject']['label']) && is_countable($extrafields->attributes['myobject']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0;
|
||||
if ($nbExtrafields > 0) {
|
||||
$head[$h][1] .= '<span class="badge marginleftonlyshort">' . $nbExtrafields . '</span>';
|
||||
}
|
||||
$head[$h][2] = 'myobject_extrafields';
|
||||
$h++;
|
||||
|
||||
$head[$h][0] = dol_buildpath("/kundenkarte/admin/myobjectline_extrafields.php", 1);
|
||||
$head[$h][1] = $langs->trans("ExtraFieldsLines");
|
||||
$nbExtrafields = (isset($extrafields->attributes['myobjectline']['label']) && is_countable($extrafields->attributes['myobjectline']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0;
|
||||
if ($nbExtrafields > 0) {
|
||||
$head[$h][1] .= '<span class="badge marginleftonlyshort">' . $nbExtrafields . '</span>';
|
||||
}
|
||||
$head[$h][2] = 'myobject_extrafieldsline';
|
||||
$h++;
|
||||
*/
|
||||
|
||||
$head[$h][0] = dol_buildpath("/kundenkarte/admin/about.php", 1);
|
||||
$head[$h][1] = $langs->trans("About");
|
||||
$head[$h][2] = 'about';
|
||||
$h++;
|
||||
|
||||
// Show more tabs from modules
|
||||
// Entries must be declared in modules descriptor with line
|
||||
//$this->tabs = array(
|
||||
// 'entity:+tabname:Title:@kundenkarte:/kundenkarte/mypage.php?id=__ID__'
|
||||
//); // to add new tab
|
||||
//$this->tabs = array(
|
||||
// 'entity:-tabname:Title:@kundenkarte:/kundenkarte/mypage.php?id=__ID__'
|
||||
//); // to remove a tab
|
||||
complete_head_from_modules($conf, $langs, null, $head, $h, 'kundenkarte@kundenkarte');
|
||||
|
||||
complete_head_from_modules($conf, $langs, null, $head, $h, 'kundenkarte@kundenkarte', 'remove');
|
||||
|
||||
return $head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an icon - either Font Awesome or custom image
|
||||
*
|
||||
* @param string $picto Icon identifier (fa-xxx or img:url)
|
||||
* @param string $alt Alt text for images
|
||||
* @param string $style Additional inline style
|
||||
* @return string HTML for the icon
|
||||
*/
|
||||
function kundenkarte_render_icon($picto, $alt = '', $style = '')
|
||||
{
|
||||
if (empty($picto)) {
|
||||
return '<i class="fa fa-cube"'.($style ? ' style="'.$style.'"' : '').'></i>';
|
||||
}
|
||||
|
||||
// Check if it's a custom image (starts with img:)
|
||||
if (strpos($picto, 'img:') === 0) {
|
||||
$url = substr($picto, 4); // Remove 'img:' prefix
|
||||
$styleAttr = 'max-width:20px;max-height:20px;vertical-align:middle;'.($style ? $style : '');
|
||||
return '<img src="'.dol_escape_htmltag($url).'" alt="'.dol_escape_htmltag($alt).'" style="'.$styleAttr.'">';
|
||||
}
|
||||
|
||||
// Font Awesome icon
|
||||
return '<i class="fa '.dol_escape_htmltag($picto).'"'.($style ? ' style="'.$style.'"' : '').'></i>';
|
||||
}
|
||||
3
modulebuilder.txt
Executable file
3
modulebuilder.txt
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
# DO NOT DELETE THIS FILE MANUALLY
|
||||
# File to flag module built using official module template.
|
||||
# When this file is present into a module directory, you can edit it with the module builder tool.
|
||||
29
sql/data.sql
Executable file
29
sql/data.sql
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Initial data for KundenKarte module
|
||||
-- ============================================================================
|
||||
|
||||
-- ============================================================================
|
||||
-- HINWEIS / NOTE:
|
||||
-- ============================================================================
|
||||
-- Diese Datei enthaelt KEINE vordefinierten Daten.
|
||||
-- This file contains NO predefined data.
|
||||
--
|
||||
-- Alle Systeme, Element-Typen und Felder koennen ueber die Admin-Seiten
|
||||
-- erstellt werden:
|
||||
-- All systems, element types and fields can be created via the admin pages:
|
||||
--
|
||||
-- - Admin > Module > KundenKarte > Anlagen-Systeme (anlage_systems.php)
|
||||
-- -> Neue System-Kategorien erstellen (z.B. Strom, Internet, Heizung...)
|
||||
-- -> Create new system categories (e.g. Electrical, Network, Heating...)
|
||||
--
|
||||
-- - Admin > Module > KundenKarte > Anlagen-Typen (anlage_types.php)
|
||||
-- -> Neue Element-Typen pro System erstellen
|
||||
-- -> Create new element types per system
|
||||
-- -> Hierarchie-Regeln definieren (Kann Kinder haben, Erlaubte Eltern)
|
||||
-- -> Define hierarchy rules (Can have children, Allowed parents)
|
||||
-- -> Felder pro Typ konfigurieren
|
||||
-- -> Configure fields per type
|
||||
--
|
||||
-- ============================================================================
|
||||
3
sql/dolibarr_allversions.sql
Executable file
3
sql/dolibarr_allversions.sql
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
--
|
||||
-- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version.
|
||||
--
|
||||
6
sql/llx_c_kundenkarte_anlage_system.key.sql
Executable file
6
sql/llx_c_kundenkarte_anlage_system.key.sql
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_c_kundenkarte_anlage_system ADD UNIQUE INDEX uk_c_kundenkarte_anlage_system_code (code, entity);
|
||||
ALTER TABLE llx_c_kundenkarte_anlage_system ADD INDEX idx_c_kundenkarte_anlage_system_active (active);
|
||||
21
sql/llx_c_kundenkarte_anlage_system.sql
Executable file
21
sql/llx_c_kundenkarte_anlage_system.sql
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Dictionary table for installation system categories
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE llx_c_kundenkarte_anlage_system
|
||||
(
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
entity integer DEFAULT 0 NOT NULL,
|
||||
|
||||
code varchar(32) NOT NULL,
|
||||
label varchar(128) NOT NULL,
|
||||
description text,
|
||||
|
||||
picto varchar(64),
|
||||
color varchar(8),
|
||||
|
||||
position integer DEFAULT 0,
|
||||
active tinyint DEFAULT 1 NOT NULL
|
||||
) ENGINE=innodb;
|
||||
16
sql/llx_kundenkarte_anlage.key.sql
Executable file
16
sql/llx_kundenkarte_anlage.key.sql
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD INDEX idx_kundenkarte_anlage_fk_soc (fk_soc);
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD INDEX idx_kundenkarte_anlage_fk_parent (fk_parent);
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD INDEX idx_kundenkarte_anlage_fk_type (fk_anlage_type);
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD INDEX idx_kundenkarte_anlage_fk_system (fk_system);
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD INDEX idx_kundenkarte_anlage_status (status);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD INDEX idx_kundenkarte_anlage_tree (fk_soc, fk_parent, rang);
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD INDEX idx_kundenkarte_anlage_system_tree (fk_soc, fk_system, fk_parent);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD CONSTRAINT fk_kundenkarte_anlage_fk_soc FOREIGN KEY (fk_soc) REFERENCES llx_societe (rowid);
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD CONSTRAINT fk_kundenkarte_anlage_fk_type FOREIGN KEY (fk_anlage_type) REFERENCES llx_kundenkarte_anlage_type (rowid);
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD CONSTRAINT fk_kundenkarte_anlage_fk_system FOREIGN KEY (fk_system) REFERENCES llx_c_kundenkarte_anlage_system (rowid);
|
||||
46
sql/llx_kundenkarte_anlage.sql
Executable file
46
sql/llx_kundenkarte_anlage.sql
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Table for customer-specific installation elements (tree structure)
|
||||
-- Uses adjacency list pattern with fk_parent for hierarchy
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE llx_kundenkarte_anlage
|
||||
(
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
entity integer DEFAULT 1 NOT NULL,
|
||||
|
||||
ref varchar(64),
|
||||
label varchar(255) NOT NULL,
|
||||
|
||||
fk_soc integer NOT NULL,
|
||||
fk_anlage_type integer NOT NULL,
|
||||
fk_parent integer DEFAULT 0 NOT NULL,
|
||||
|
||||
fk_system integer NOT NULL,
|
||||
|
||||
manufacturer varchar(128),
|
||||
model varchar(128),
|
||||
serial_number varchar(64),
|
||||
power_rating varchar(32),
|
||||
|
||||
field_values text,
|
||||
|
||||
location varchar(255),
|
||||
installation_date date,
|
||||
warranty_until date,
|
||||
|
||||
rang integer DEFAULT 0,
|
||||
level integer DEFAULT 0,
|
||||
|
||||
note_private text,
|
||||
note_public text,
|
||||
|
||||
status tinyint DEFAULT 1 NOT NULL,
|
||||
|
||||
date_creation datetime,
|
||||
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
fk_user_creat integer,
|
||||
fk_user_modif integer,
|
||||
import_key varchar(14)
|
||||
) ENGINE=innodb;
|
||||
8
sql/llx_kundenkarte_anlage_contact.sql
Normal file
8
sql/llx_kundenkarte_anlage_contact.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Add fk_contact column for contact/address specific installations
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD COLUMN fk_contact integer DEFAULT NULL AFTER fk_soc;
|
||||
ALTER TABLE llx_kundenkarte_anlage ADD INDEX idx_kundenkarte_anlage_fk_contact (fk_contact);
|
||||
11
sql/llx_kundenkarte_anlage_files.key.sql
Executable file
11
sql/llx_kundenkarte_anlage_files.key.sql
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_files ADD INDEX idx_kundenkarte_anlage_files_fk_anlage (fk_anlage);
|
||||
ALTER TABLE llx_kundenkarte_anlage_files ADD INDEX idx_kundenkarte_anlage_files_type (file_type);
|
||||
ALTER TABLE llx_kundenkarte_anlage_files ADD INDEX idx_kundenkarte_anlage_files_share (share);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_files ADD UNIQUE INDEX uk_kundenkarte_anlage_files (fk_anlage, filename);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_files ADD CONSTRAINT fk_kundenkarte_anlage_files_fk_anlage FOREIGN KEY (fk_anlage) REFERENCES llx_kundenkarte_anlage (rowid) ON DELETE CASCADE;
|
||||
35
sql/llx_kundenkarte_anlage_files.sql
Executable file
35
sql/llx_kundenkarte_anlage_files.sql
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Table for installation element file attachments
|
||||
-- Files stored in: /documents/kundenkarte/anlagen/{socid}/{element_id}/
|
||||
-- Separate from ECM system
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE llx_kundenkarte_anlage_files
|
||||
(
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
entity integer DEFAULT 1 NOT NULL,
|
||||
|
||||
fk_anlage integer NOT NULL,
|
||||
|
||||
filename varchar(255) NOT NULL,
|
||||
filepath varchar(512) NOT NULL,
|
||||
filesize bigint DEFAULT 0,
|
||||
mimetype varchar(128),
|
||||
|
||||
file_type varchar(32) DEFAULT 'document',
|
||||
|
||||
label varchar(255),
|
||||
description text,
|
||||
|
||||
is_cover tinyint DEFAULT 0 NOT NULL,
|
||||
position integer DEFAULT 0,
|
||||
|
||||
share varchar(128),
|
||||
|
||||
date_creation datetime,
|
||||
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
fk_user_creat integer,
|
||||
fk_user_modif integer
|
||||
) ENGINE=innodb;
|
||||
10
sql/llx_kundenkarte_anlage_type.key.sql
Executable file
10
sql/llx_kundenkarte_anlage_type.key.sql
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_type ADD UNIQUE INDEX uk_kundenkarte_anlage_type_ref (ref, entity);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_type ADD INDEX idx_kundenkarte_anlage_type_fk_system (fk_system);
|
||||
ALTER TABLE llx_kundenkarte_anlage_type ADD INDEX idx_kundenkarte_anlage_type_active (active);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_type ADD CONSTRAINT fk_kundenkarte_anlage_type_fk_system FOREIGN KEY (fk_system) REFERENCES llx_c_kundenkarte_anlage_system (rowid);
|
||||
37
sql/llx_kundenkarte_anlage_type.sql
Executable file
37
sql/llx_kundenkarte_anlage_type.sql
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Table for installation element type templates (globally reusable)
|
||||
-- Examples: Zaehlerschrank, Unterverteilung, Wallbox, Router, etc.
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE llx_kundenkarte_anlage_type
|
||||
(
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
entity integer DEFAULT 0 NOT NULL,
|
||||
|
||||
ref varchar(64) NOT NULL,
|
||||
label varchar(255) NOT NULL,
|
||||
label_short varchar(64),
|
||||
description text,
|
||||
|
||||
fk_system integer NOT NULL,
|
||||
|
||||
can_have_children tinyint DEFAULT 0 NOT NULL,
|
||||
can_be_nested tinyint DEFAULT 0 NOT NULL,
|
||||
allowed_parent_types varchar(255),
|
||||
|
||||
picto varchar(64),
|
||||
color varchar(8),
|
||||
|
||||
is_system tinyint DEFAULT 0 NOT NULL,
|
||||
|
||||
position integer DEFAULT 0,
|
||||
active tinyint DEFAULT 1 NOT NULL,
|
||||
|
||||
date_creation datetime,
|
||||
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
fk_user_creat integer,
|
||||
fk_user_modif integer,
|
||||
import_key varchar(14)
|
||||
) ENGINE=innodb;
|
||||
10
sql/llx_kundenkarte_anlage_type_field.key.sql
Executable file
10
sql/llx_kundenkarte_anlage_type_field.key.sql
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_type_field ADD UNIQUE INDEX uk_kundenkarte_anlage_type_field (fk_anlage_type, field_code);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_type_field ADD INDEX idx_kundenkarte_anlage_type_field_fk_type (fk_anlage_type);
|
||||
ALTER TABLE llx_kundenkarte_anlage_type_field ADD INDEX idx_kundenkarte_anlage_type_field_active (active);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_anlage_type_field ADD CONSTRAINT fk_kundenkarte_anlage_type_field_fk_type FOREIGN KEY (fk_anlage_type) REFERENCES llx_kundenkarte_anlage_type (rowid) ON DELETE CASCADE;
|
||||
33
sql/llx_kundenkarte_anlage_type_field.sql
Executable file
33
sql/llx_kundenkarte_anlage_type_field.sql
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Table for defining fields per element type (EAV pattern for flexibility)
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE llx_kundenkarte_anlage_type_field
|
||||
(
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
entity integer DEFAULT 0 NOT NULL,
|
||||
|
||||
fk_anlage_type integer NOT NULL,
|
||||
|
||||
field_code varchar(64) NOT NULL,
|
||||
field_label varchar(255) NOT NULL,
|
||||
field_type varchar(32) NOT NULL,
|
||||
field_options text,
|
||||
|
||||
field_size integer DEFAULT 255,
|
||||
field_default varchar(255),
|
||||
|
||||
required tinyint DEFAULT 0 NOT NULL,
|
||||
show_in_tree tinyint DEFAULT 0 NOT NULL,
|
||||
show_in_hover tinyint DEFAULT 1 NOT NULL,
|
||||
|
||||
position integer DEFAULT 0,
|
||||
active tinyint DEFAULT 1 NOT NULL,
|
||||
|
||||
date_creation datetime,
|
||||
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
fk_user_creat integer,
|
||||
fk_user_modif integer
|
||||
) ENGINE=innodb;
|
||||
12
sql/llx_kundenkarte_favorite_products.key.sql
Executable file
12
sql/llx_kundenkarte_favorite_products.key.sql
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_favorite_products ADD UNIQUE INDEX uk_kundenkarte_favprod_soc_product (fk_soc, fk_product, entity);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_favorite_products ADD INDEX idx_kundenkarte_favprod_fk_soc (fk_soc);
|
||||
ALTER TABLE llx_kundenkarte_favorite_products ADD INDEX idx_kundenkarte_favprod_fk_product (fk_product);
|
||||
ALTER TABLE llx_kundenkarte_favorite_products ADD INDEX idx_kundenkarte_favprod_active (active);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_favorite_products ADD CONSTRAINT fk_kundenkarte_favprod_fk_soc FOREIGN KEY (fk_soc) REFERENCES llx_societe (rowid);
|
||||
ALTER TABLE llx_kundenkarte_favorite_products ADD CONSTRAINT fk_kundenkarte_favprod_fk_product FOREIGN KEY (fk_product) REFERENCES llx_product (rowid);
|
||||
32
sql/llx_kundenkarte_favorite_products.sql
Executable file
32
sql/llx_kundenkarte_favorite_products.sql
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- This program is free software; you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation; either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- Table for storing customer favorite products
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE llx_kundenkarte_favorite_products
|
||||
(
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
entity integer DEFAULT 1 NOT NULL,
|
||||
|
||||
fk_soc integer NOT NULL,
|
||||
fk_product integer NOT NULL,
|
||||
|
||||
qty double(24,8) DEFAULT 1 NOT NULL,
|
||||
|
||||
rang integer DEFAULT 0,
|
||||
note text,
|
||||
|
||||
active tinyint DEFAULT 1 NOT NULL,
|
||||
|
||||
date_creation datetime,
|
||||
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
fk_user_creat integer,
|
||||
fk_user_modif integer,
|
||||
import_key varchar(14)
|
||||
) ENGINE=innodb;
|
||||
8
sql/llx_kundenkarte_favorite_products_contact.sql
Normal file
8
sql/llx_kundenkarte_favorite_products_contact.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Add fk_contact column for contact/address specific favorites
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_favorite_products ADD COLUMN fk_contact integer DEFAULT NULL AFTER fk_soc;
|
||||
ALTER TABLE llx_kundenkarte_favorite_products ADD INDEX idx_kundenkarte_favorite_products_fk_contact (fk_contact);
|
||||
12
sql/llx_kundenkarte_societe_system.key.sql
Executable file
12
sql/llx_kundenkarte_societe_system.key.sql
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Keys for llx_kundenkarte_societe_system
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_societe_system ADD INDEX idx_kundenkarte_societe_system_fk_soc (fk_soc);
|
||||
ALTER TABLE llx_kundenkarte_societe_system ADD INDEX idx_kundenkarte_societe_system_fk_system (fk_system);
|
||||
ALTER TABLE llx_kundenkarte_societe_system ADD UNIQUE INDEX uk_kundenkarte_societe_system (fk_soc, fk_system);
|
||||
|
||||
ALTER TABLE llx_kundenkarte_societe_system ADD CONSTRAINT fk_kundenkarte_societe_system_soc FOREIGN KEY (fk_soc) REFERENCES llx_societe(rowid);
|
||||
ALTER TABLE llx_kundenkarte_societe_system ADD CONSTRAINT fk_kundenkarte_societe_system_system FOREIGN KEY (fk_system) REFERENCES llx_c_kundenkarte_anlage_system(rowid);
|
||||
18
sql/llx_kundenkarte_societe_system.sql
Executable file
18
sql/llx_kundenkarte_societe_system.sql
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Links customers (societe) to installation systems
|
||||
-- Defines which systems are present at a specific customer location
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE llx_kundenkarte_societe_system (
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
entity integer DEFAULT 1 NOT NULL,
|
||||
fk_soc integer NOT NULL, -- Customer (llx_societe)
|
||||
fk_system integer NOT NULL, -- System category (llx_c_kundenkarte_anlage_system)
|
||||
note text, -- Optional notes
|
||||
date_creation datetime NOT NULL,
|
||||
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
fk_user_creat integer,
|
||||
active tinyint DEFAULT 1 NOT NULL
|
||||
) ENGINE=innodb;
|
||||
8
sql/llx_kundenkarte_societe_system_contact.sql
Normal file
8
sql/llx_kundenkarte_societe_system_contact.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- ============================================================================
|
||||
-- Copyright (C) 2026 Alles Watt lauft
|
||||
--
|
||||
-- Add fk_contact column for contact/address specific system assignments
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE llx_kundenkarte_societe_system ADD COLUMN fk_contact integer DEFAULT NULL AFTER fk_soc;
|
||||
ALTER TABLE llx_kundenkarte_societe_system ADD INDEX idx_kundenkarte_societe_system_fk_contact (fk_contact);
|
||||
802
tabs/anlagen.php
Executable file
802
tabs/anlagen.php
Executable file
|
|
@ -0,0 +1,802 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file tabs/anlagen.php
|
||||
* \brief Tab for technical installations on thirdparty card
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||
dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||
dol_include_once('/kundenkarte/class/anlagetype.class.php');
|
||||
dol_include_once('/kundenkarte/class/anlagefile.class.php');
|
||||
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
||||
|
||||
// Load translation files
|
||||
$langs->loadLangs(array('companies', 'kundenkarte@kundenkarte'));
|
||||
|
||||
// Get parameters
|
||||
$id = GETPOSTINT('id');
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
$systemId = GETPOSTINT('system');
|
||||
$anlageId = GETPOSTINT('anlage_id');
|
||||
$parentId = GETPOSTINT('parent_id');
|
||||
|
||||
// Security check
|
||||
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Initialize objects
|
||||
$object = new Societe($db);
|
||||
$form = new Form($db);
|
||||
$anlage = new Anlage($db);
|
||||
$anlageType = new AnlageType($db);
|
||||
|
||||
// Load thirdparty
|
||||
if ($id > 0) {
|
||||
$result = $object->fetch($id);
|
||||
if ($result <= 0) {
|
||||
dol_print_error($db, $object->error);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$permissiontoread = $user->hasRight('kundenkarte', 'read');
|
||||
$permissiontoadd = $user->hasRight('kundenkarte', 'write');
|
||||
$permissiontodelete = $user->hasRight('kundenkarte', 'delete');
|
||||
|
||||
// Load ALL available systems (from dictionary)
|
||||
$allSystems = array();
|
||||
$sql = "SELECT rowid, code, label, picto, color FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system WHERE active = 1 ORDER BY position ASC";
|
||||
$resql = $db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$allSystems[$obj->rowid] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
// Load systems ENABLED for this customer (only thirdparty-level, not contact-specific)
|
||||
$customerSystems = array();
|
||||
$sql = "SELECT ss.rowid, ss.fk_system, s.code, s.label, s.picto, s.color
|
||||
FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system ss
|
||||
JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system s ON s.rowid = ss.fk_system
|
||||
WHERE ss.fk_soc = ".((int) $id)." AND (ss.fk_contact IS NULL OR ss.fk_contact = 0) AND ss.active = 1 AND s.active = 1
|
||||
ORDER BY s.position ASC";
|
||||
$resql = $db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$customerSystems[$obj->fk_system] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to first enabled system if not specified
|
||||
if (empty($systemId) && !empty($customerSystems)) {
|
||||
$systemId = array_key_first($customerSystems);
|
||||
}
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
// Add system to customer
|
||||
if ($action == 'add_system' && $permissiontoadd) {
|
||||
$newSystemId = GETPOSTINT('new_system_id');
|
||||
if ($newSystemId > 0 && !isset($customerSystems[$newSystemId])) {
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX."kundenkarte_societe_system";
|
||||
$sql .= " (entity, fk_soc, fk_system, date_creation, fk_user_creat, active)";
|
||||
$sql .= " VALUES (".$conf->entity.", ".((int) $id).", ".((int) $newSystemId).", NOW(), ".((int) $user->id).", 1)";
|
||||
$result = $db->query($sql);
|
||||
if ($result) {
|
||||
setEventMessages($langs->trans('SystemAdded'), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$newSystemId);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($db->lasterror(), null, 'errors');
|
||||
}
|
||||
}
|
||||
$action = '';
|
||||
}
|
||||
|
||||
// Remove system from customer
|
||||
if ($action == 'confirm_remove_system' && $confirm == 'yes' && $permissiontodelete) {
|
||||
$removeSystemId = GETPOSTINT('remove_system_id');
|
||||
if ($removeSystemId > 0) {
|
||||
// Check if system has any elements (only thirdparty-level, not contact-specific)
|
||||
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."kundenkarte_anlage WHERE fk_soc = ".((int) $id)." AND (fk_contact IS NULL OR fk_contact = 0) AND fk_system = ".((int) $removeSystemId);
|
||||
$resql = $db->query($sql);
|
||||
$obj = $db->fetch_object($resql);
|
||||
|
||||
if ($obj->cnt > 0) {
|
||||
setEventMessages($langs->trans('ErrorSystemHasElements'), null, 'errors');
|
||||
} else {
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system WHERE fk_soc = ".((int) $id)." AND (fk_contact IS NULL OR fk_contact = 0) AND fk_system = ".((int) $removeSystemId);
|
||||
$db->query($sql);
|
||||
setEventMessages($langs->trans('SystemRemoved'), null, 'mesgs');
|
||||
|
||||
// Switch to another system or none
|
||||
unset($customerSystems[$removeSystemId]);
|
||||
if (!empty($customerSystems)) {
|
||||
$systemId = array_key_first($customerSystems);
|
||||
} else {
|
||||
$systemId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.($systemId ? '&system='.$systemId : ''));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'add' && $permissiontoadd) {
|
||||
$anlage->label = GETPOST('label', 'alphanohtml');
|
||||
$anlage->fk_soc = $id;
|
||||
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||
$anlage->fk_system = $systemId;
|
||||
$anlage->manufacturer = GETPOST('manufacturer', 'alphanohtml');
|
||||
$anlage->model = GETPOST('model', 'alphanohtml');
|
||||
$anlage->serial_number = GETPOST('serial_number', 'alphanohtml');
|
||||
$anlage->power_rating = GETPOST('power_rating', 'alphanohtml');
|
||||
$anlage->location = GETPOST('location', 'alphanohtml');
|
||||
$anlage->installation_date = dol_mktime(0, 0, 0, GETPOSTINT('installation_datemonth'), GETPOSTINT('installation_dateday'), GETPOSTINT('installation_dateyear'));
|
||||
$anlage->note_private = GETPOST('note_private', 'restricthtml');
|
||||
$anlage->status = 1;
|
||||
|
||||
// Get type for system ID
|
||||
$type = new AnlageType($db);
|
||||
if ($type->fetch($anlage->fk_anlage_type) > 0) {
|
||||
$anlage->fk_system = $type->fk_system;
|
||||
}
|
||||
|
||||
// Dynamic fields
|
||||
$fieldValues = array();
|
||||
$fields = $type->fetchFields();
|
||||
foreach ($fields as $field) {
|
||||
$value = GETPOST('field_'.$field->field_code, 'alphanohtml');
|
||||
if ($value !== '') {
|
||||
$fieldValues[$field->field_code] = $value;
|
||||
}
|
||||
}
|
||||
$anlage->setFieldValues($fieldValues);
|
||||
|
||||
$result = $anlage->create($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$anlage->fk_system);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||
$action = 'create';
|
||||
}
|
||||
}
|
||||
|
||||
if ($action == 'update' && $permissiontoadd) {
|
||||
$anlage->fetch($anlageId);
|
||||
$anlage->label = GETPOST('label', 'alphanohtml');
|
||||
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||
$anlage->manufacturer = GETPOST('manufacturer', 'alphanohtml');
|
||||
$anlage->model = GETPOST('model', 'alphanohtml');
|
||||
$anlage->serial_number = GETPOST('serial_number', 'alphanohtml');
|
||||
$anlage->power_rating = GETPOST('power_rating', 'alphanohtml');
|
||||
$anlage->location = GETPOST('location', 'alphanohtml');
|
||||
$anlage->installation_date = dol_mktime(0, 0, 0, GETPOSTINT('installation_datemonth'), GETPOSTINT('installation_dateday'), GETPOSTINT('installation_dateyear'));
|
||||
$anlage->note_private = GETPOST('note_private', 'restricthtml');
|
||||
|
||||
// Get type for system ID
|
||||
$type = new AnlageType($db);
|
||||
if ($type->fetch($anlage->fk_anlage_type) > 0) {
|
||||
$anlage->fk_system = $type->fk_system;
|
||||
}
|
||||
|
||||
// Dynamic fields
|
||||
$fieldValues = array();
|
||||
$fields = $type->fetchFields();
|
||||
foreach ($fields as $field) {
|
||||
$value = GETPOST('field_'.$field->field_code, 'alphanohtml');
|
||||
if ($value !== '') {
|
||||
$fieldValues[$field->field_code] = $value;
|
||||
}
|
||||
}
|
||||
$anlage->setFieldValues($fieldValues);
|
||||
|
||||
$result = $anlage->update($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$anlage->fk_system);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||
$action = 'edit';
|
||||
}
|
||||
}
|
||||
|
||||
if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodelete) {
|
||||
$anlage->fetch($anlageId);
|
||||
$systemIdBefore = $anlage->fk_system;
|
||||
$result = $anlage->delete($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemIdBefore);
|
||||
exit;
|
||||
}
|
||||
|
||||
// File upload
|
||||
if ($action == 'uploadfile' && $permissiontoadd) {
|
||||
$anlage->fetch($anlageId);
|
||||
$upload_dir = $anlage->getFileDirectory();
|
||||
|
||||
// Create directory if not exists
|
||||
if (!is_dir($upload_dir)) {
|
||||
dol_mkdir($upload_dir);
|
||||
}
|
||||
|
||||
if (!empty($_FILES['userfile']['name'])) {
|
||||
$result = dol_add_file_process($upload_dir, 0, 1, 'userfile', '', null, '', 1);
|
||||
if ($result > 0) {
|
||||
// Add to database
|
||||
$anlagefile = new AnlageFile($db);
|
||||
$anlagefile->fk_anlage = $anlageId;
|
||||
$anlagefile->filename = dol_sanitizeFileName($_FILES['userfile']['name']);
|
||||
// IMPORTANT: Store ONLY relative path (anlagen/socid/anlageid/filename) - never full path!
|
||||
$anlagefile->filepath = 'anlagen/'.$anlage->fk_soc.'/'.$anlage->id.'/'.$anlagefile->filename;
|
||||
$anlagefile->filesize = $_FILES['userfile']['size'];
|
||||
$anlagefile->mimetype = dol_mimetype($anlagefile->filename);
|
||||
$anlagefile->create($user);
|
||||
|
||||
// Generate thumbnail
|
||||
$anlagefile->generateThumbnail();
|
||||
|
||||
setEventMessages($langs->trans('FileUploaded'), null, 'mesgs');
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=view&anlage_id='.$anlageId);
|
||||
exit;
|
||||
}
|
||||
|
||||
// File delete (after confirmation)
|
||||
if ($action == 'confirm_deletefile' && $confirm == 'yes' && $permissiontodelete) {
|
||||
$fileId = GETPOSTINT('fileid');
|
||||
if ($fileId > 0) {
|
||||
$anlagefile = new AnlageFile($db);
|
||||
if ($anlagefile->fetch($fileId) > 0) {
|
||||
// Delete method handles physical file and database
|
||||
if ($anlagefile->delete($user) > 0) {
|
||||
setEventMessages($langs->trans('FileDeleted'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($langs->trans('Error'), null, 'errors');
|
||||
}
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=view&anlage_id='.$anlageId);
|
||||
exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$title = $langs->trans('TechnicalInstallations').' - '.$object->name;
|
||||
llxHeader('', $title, '', '', 0, 0, array('/kundenkarte/js/kundenkarte.js'), array('/kundenkarte/css/kundenkarte.css'));
|
||||
|
||||
// Prepare tabs
|
||||
$head = societe_prepare_head($object);
|
||||
|
||||
print dol_get_fiche_head($head, 'anlagen', $langs->trans("ThirdParty"), -1, 'company');
|
||||
|
||||
// Thirdparty card
|
||||
$linkback = '<a href="'.DOL_URL_ROOT.'/societe/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
|
||||
dol_banner_tab($object, 'socid', $linkback, ($user->socid ? 0 : 1), 'rowid', 'nom');
|
||||
|
||||
print '<div class="fichecenter">';
|
||||
|
||||
// Confirmation dialogs
|
||||
if ($action == 'delete') {
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?id='.$id.'&anlage_id='.$anlageId.'&system='.$systemId,
|
||||
$langs->trans('DeleteElement'),
|
||||
$langs->trans('ConfirmDeleteElement'),
|
||||
'confirm_delete',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
if ($action == 'remove_system') {
|
||||
$removeSystemId = GETPOSTINT('remove_system_id');
|
||||
$sysLabel = isset($customerSystems[$removeSystemId]) ? $customerSystems[$removeSystemId]->label : '';
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?id='.$id.'&remove_system_id='.$removeSystemId,
|
||||
$langs->trans('RemoveSystem'),
|
||||
$langs->trans('ConfirmRemoveSystem', $sysLabel),
|
||||
'confirm_remove_system',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
if ($action == 'askdeletefile') {
|
||||
$fileId = GETPOSTINT('fileid');
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&anlage_id='.$anlageId.'&fileid='.$fileId,
|
||||
$langs->trans('Delete'),
|
||||
$langs->trans('ConfirmDeleteFile'),
|
||||
'confirm_deletefile',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
// System tabs (only show enabled systems for this customer)
|
||||
print '<div class="kundenkarte-system-tabs-wrapper">';
|
||||
print '<div class="kundenkarte-system-tabs">';
|
||||
foreach ($customerSystems as $sysId => $sys) {
|
||||
$activeClass = ($sysId == $systemId) ? ' active' : '';
|
||||
print '<div class="kundenkarte-system-tab'.$activeClass.'" data-system="'.$sysId.'">';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$sysId.'" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:8px;">';
|
||||
if ($sys->picto) {
|
||||
print '<span class="kundenkarte-system-tab-icon" style="color:'.$sys->color.';">'.kundenkarte_render_icon($sys->picto).'</span>';
|
||||
}
|
||||
print '<span>'.dol_escape_htmltag($sys->label).'</span>';
|
||||
print '</a>';
|
||||
// Remove button (only if no elements)
|
||||
if ($permissiontodelete && $sysId == $systemId) {
|
||||
print ' <a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=remove_system&remove_system_id='.$sysId.'" class="kundenkarte-system-remove" title="'.$langs->trans('RemoveSystem').'"><i class="fa fa-times"></i></a>';
|
||||
}
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Add system button (always on the right)
|
||||
if ($permissiontoadd) {
|
||||
// Get systems not yet enabled for this customer
|
||||
$availableSystems = array_diff_key($allSystems, $customerSystems);
|
||||
if (!empty($availableSystems)) {
|
||||
print '<button type="button" class="button small" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';" style="margin-left:auto;">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
|
||||
print '</button>';
|
||||
}
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
// Expand/Collapse buttons (only in tree view, not in create/edit/view)
|
||||
$isTreeView = !in_array($action, array('create', 'edit', 'view'));
|
||||
if ($isTreeView) {
|
||||
print '<div class="kundenkarte-tree-controls">';
|
||||
print '<button type="button" class="button small" id="btn-expand-all" title="'.$langs->trans('ExpandAll').'">';
|
||||
print '<i class="fa fa-expand"></i> '.$langs->trans('ExpandAll');
|
||||
print '</button>';
|
||||
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
|
||||
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
|
||||
print '</button>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print '</div>'; // End kundenkarte-system-tabs-wrapper
|
||||
|
||||
// Add system form (hidden by default)
|
||||
if ($permissiontoadd && !empty($availableSystems)) {
|
||||
print '<div id="add-system-form" class="kundenkarte-add-system-form" style="display:none;margin-bottom:15px;padding:10px;background:#f9f9f9;border-radius:6px;">';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="add_system">';
|
||||
print '<strong>'.$langs->trans('SelectSystemToAdd').':</strong> ';
|
||||
print '<select name="new_system_id" class="flat">';
|
||||
print '<option value="">'.$langs->trans('Select').'</option>';
|
||||
foreach ($availableSystems as $avSys) {
|
||||
print '<option value="'.$avSys->rowid.'">'.dol_escape_htmltag($avSys->label).'</option>';
|
||||
}
|
||||
print '</select>';
|
||||
print ' <button type="submit" class="button small">'.$langs->trans('Add').'</button>';
|
||||
print ' <button type="button" class="button small button-cancel" onclick="document.getElementById(\'add-system-form\').style.display=\'none\';">'.$langs->trans('Cancel').'</button>';
|
||||
print '</form>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Check if customer has any systems
|
||||
if (empty($customerSystems)) {
|
||||
print '<div class="opacitymedium" style="padding:20px;text-align:center;">';
|
||||
print '<i class="fa fa-info-circle" style="font-size:24px;margin-bottom:10px;"></i><br>';
|
||||
print $langs->trans('NoSystemsConfigured').'<br><br>';
|
||||
if ($permissiontoadd && !empty($allSystems)) {
|
||||
print $langs->trans('ClickAddSystemToStart');
|
||||
} else {
|
||||
print $langs->trans('ContactAdminToAddSystems');
|
||||
}
|
||||
print '</div>';
|
||||
} elseif ($systemId > 0) {
|
||||
// Show form or tree for selected system
|
||||
if (in_array($action, array('create', 'edit', 'view'))) {
|
||||
// Load element for edit/view
|
||||
if ($action != 'create' && $anlageId > 0) {
|
||||
$anlage->fetch($anlageId);
|
||||
$type = new AnlageType($db);
|
||||
$type->fetch($anlage->fk_anlage_type);
|
||||
$type->fetchFields();
|
||||
}
|
||||
|
||||
// Load types for select
|
||||
$types = $anlageType->fetchAllBySystem($systemId);
|
||||
|
||||
print '<div class="kundenkarte-element-form">';
|
||||
|
||||
if ($action == 'view') {
|
||||
// View mode
|
||||
print '<h3>'.dol_escape_htmltag($anlage->label).'</h3>';
|
||||
|
||||
print '<table class="border centpercent">';
|
||||
|
||||
print '<tr><td class="titlefield">'.$langs->trans('Type').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->type_label).'</td></tr>';
|
||||
|
||||
if ($anlage->manufacturer) {
|
||||
print '<tr><td>'.$langs->trans('FieldManufacturer').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->manufacturer).'</td></tr>';
|
||||
}
|
||||
if ($anlage->model) {
|
||||
print '<tr><td>'.$langs->trans('FieldModel').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->model).'</td></tr>';
|
||||
}
|
||||
if ($anlage->serial_number) {
|
||||
print '<tr><td>'.$langs->trans('FieldSerialNumber').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->serial_number).'</td></tr>';
|
||||
}
|
||||
if ($anlage->power_rating) {
|
||||
print '<tr><td>'.$langs->trans('FieldPowerRating').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->power_rating).'</td></tr>';
|
||||
}
|
||||
if ($anlage->location) {
|
||||
print '<tr><td>'.$langs->trans('FieldLocation').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->location).'</td></tr>';
|
||||
}
|
||||
if ($anlage->installation_date) {
|
||||
print '<tr><td>'.$langs->trans('FieldInstallationDate').'</td>';
|
||||
print '<td>'.dol_print_date($anlage->installation_date, 'day').'</td></tr>';
|
||||
}
|
||||
|
||||
// Dynamic fields
|
||||
$fieldValues = $anlage->getFieldValues();
|
||||
foreach ($type->fields as $field) {
|
||||
if (isset($fieldValues[$field->field_code]) && $fieldValues[$field->field_code] !== '') {
|
||||
print '<tr><td>'.dol_escape_htmltag($field->field_label).'</td>';
|
||||
$value = $fieldValues[$field->field_code];
|
||||
// For select fields, show the label
|
||||
if ($field->field_type == 'select' && $field->field_options) {
|
||||
$options = json_decode($field->field_options, true);
|
||||
if (isset($options['options'][$value])) {
|
||||
$value = $options['options'][$value];
|
||||
}
|
||||
}
|
||||
print '<td>'.dol_escape_htmltag($value).'</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($anlage->note_private) {
|
||||
print '<tr><td>'.$langs->trans('FieldNotes').'</td>';
|
||||
print '<td>'.dol_htmlentitiesbr($anlage->note_private).'</td></tr>';
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Files section
|
||||
$anlagefile = new AnlageFile($db);
|
||||
$files = $anlagefile->fetchAllByAnlage($anlageId);
|
||||
|
||||
print '<br><h4>'.$langs->trans('AttachedFiles').'</h4>';
|
||||
|
||||
if ($permissiontoadd) {
|
||||
print '<form method="POST" enctype="multipart/form-data" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=uploadfile&anlage_id='.$anlageId.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="file" name="userfile" accept="image/*,.pdf,.doc,.docx">';
|
||||
print ' <button type="submit" class="button">'.$langs->trans('Upload').'</button>';
|
||||
print '</form><br>';
|
||||
}
|
||||
|
||||
if (!empty($files)) {
|
||||
print '<div class="kundenkarte-files-grid">';
|
||||
foreach ($files as $file) {
|
||||
print '<div class="kundenkarte-file-item">';
|
||||
print '<div class="kundenkarte-file-preview">';
|
||||
if ($file->file_type == 'image') {
|
||||
$thumbUrl = $file->getThumbUrl();
|
||||
if ($thumbUrl) {
|
||||
print '<img src="'.$thumbUrl.'" alt="">';
|
||||
} else {
|
||||
print '<img src="'.$file->getUrl().'" alt="" style="max-width:100%;max-height:100%;">';
|
||||
}
|
||||
} elseif ($file->file_type == 'pdf') {
|
||||
// PDF preview using iframe - 50% smaller, no toolbar
|
||||
print '<div class="kundenkarte-pdf-preview-wrapper">';
|
||||
print '<iframe src="'.$file->getUrl().'#page=1&toolbar=0&navpanes=0&statusbar=0&view=FitH" class="kundenkarte-pdf-preview-frame"></iframe>';
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<i class="fa fa-file-o" style="font-size:48px;color:#999;"></i>';
|
||||
}
|
||||
print '</div>';
|
||||
print '<div class="kundenkarte-file-info">';
|
||||
print '<div class="kundenkarte-file-name" title="'.dol_escape_htmltag($file->filename).'">'.dol_escape_htmltag(dol_trunc($file->filename, 20)).'</div>';
|
||||
print '<div class="kundenkarte-file-size">'.dol_print_size($file->filesize).'</div>';
|
||||
print '<div class="kundenkarte-file-actions">';
|
||||
print '<a href="'.$file->getUrl().'" target="_blank" class="kundenkarte-file-btn" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||
if ($permissiontodelete) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=askdeletefile&anlage_id='.$anlageId.'&fileid='.$file->id.'" class="kundenkarte-file-btn kundenkarte-file-btn-delete" title="'.$langs->trans('Delete').'"><i class="fa fa-trash"></i></a>';
|
||||
}
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
}
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<p class="opacitymedium">'.$langs->trans('NoFiles').'</p>';
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
print '<div class="tabsAction">';
|
||||
if ($permissiontoadd) {
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=edit&anlage_id='.$anlageId.'">'.$langs->trans('Modify').'</a>';
|
||||
}
|
||||
if ($permissiontodelete) {
|
||||
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=delete&anlage_id='.$anlageId.'">'.$langs->trans('Delete').'</a>';
|
||||
}
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'">'.$langs->trans('Back').'</a>';
|
||||
print '</div>';
|
||||
|
||||
} else {
|
||||
// Create/Edit form
|
||||
$formAction = ($action == 'create') ? 'add' : 'update';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="'.$formAction.'">';
|
||||
if ($action == 'edit') {
|
||||
print '<input type="hidden" name="anlage_id" value="'.$anlageId.'">';
|
||||
}
|
||||
|
||||
print '<table class="border centpercent">';
|
||||
|
||||
// Label
|
||||
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('Label').'</td>';
|
||||
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->label : GETPOST('label')).'" required></td></tr>';
|
||||
|
||||
// Type
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans('Type').'</td>';
|
||||
print '<td><select name="fk_anlage_type" class="flat minwidth200" id="select_type" required>';
|
||||
print '<option value="">'.$langs->trans('SelectType').'</option>';
|
||||
foreach ($types as $t) {
|
||||
$selected = ($action == 'edit' && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||
print '<option value="'.$t->id.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||
}
|
||||
print '</select>';
|
||||
if (empty($types)) {
|
||||
print '<br><span class="warning">'.$langs->trans('NoTypesDefinedForSystem').'</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
// Parent
|
||||
$tree = $anlage->fetchTree($id, $systemId);
|
||||
print '<tr><td>'.$langs->trans('SelectParent').'</td>';
|
||||
print '<td><select name="fk_parent" class="flat minwidth200">';
|
||||
print '<option value="0">('.$langs->trans('Root').')</option>';
|
||||
printTreeOptions($tree, $action == 'edit' ? $anlage->fk_parent : $parentId, $action == 'edit' ? $anlageId : 0);
|
||||
print '</select></td></tr>';
|
||||
|
||||
// Manufacturer
|
||||
print '<tr><td>'.$langs->trans('FieldManufacturer').'</td>';
|
||||
print '<td><input type="text" name="manufacturer" class="flat minwidth200" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->manufacturer : GETPOST('manufacturer')).'"></td></tr>';
|
||||
|
||||
// Model
|
||||
print '<tr><td>'.$langs->trans('FieldModel').'</td>';
|
||||
print '<td><input type="text" name="model" class="flat minwidth200" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->model : GETPOST('model')).'"></td></tr>';
|
||||
|
||||
// Serial number
|
||||
print '<tr><td>'.$langs->trans('FieldSerialNumber').'</td>';
|
||||
print '<td><input type="text" name="serial_number" class="flat minwidth200" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->serial_number : GETPOST('serial_number')).'"></td></tr>';
|
||||
|
||||
// Power rating
|
||||
print '<tr><td>'.$langs->trans('FieldPowerRating').'</td>';
|
||||
print '<td><input type="text" name="power_rating" class="flat minwidth200" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->power_rating : GETPOST('power_rating')).'"></td></tr>';
|
||||
|
||||
// Location
|
||||
print '<tr><td>'.$langs->trans('FieldLocation').'</td>';
|
||||
print '<td><input type="text" name="location" class="flat minwidth300" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->location : GETPOST('location')).'"></td></tr>';
|
||||
|
||||
// Installation date
|
||||
print '<tr><td>'.$langs->trans('FieldInstallationDate').'</td>';
|
||||
print '<td>'.$form->selectDate($action == 'edit' ? $anlage->installation_date : '', 'installation_date', 0, 0, 1, '', 1, 1).'</td></tr>';
|
||||
|
||||
// Notes
|
||||
print '<tr><td>'.$langs->trans('FieldNotes').'</td>';
|
||||
print '<td><textarea name="note_private" class="flat minwidth300" rows="3">'.dol_escape_htmltag($action == 'edit' ? $anlage->note_private : GETPOST('note_private')).'</textarea></td></tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Dynamic fields will be loaded via JavaScript based on type selection
|
||||
print '<div id="dynamic_fields"></div>';
|
||||
|
||||
print '<div class="center" style="margin-top:20px;">';
|
||||
print '<button type="submit" class="button">'.$langs->trans('Save').'</button>';
|
||||
print ' <a class="button button-cancel" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'">'.$langs->trans('Cancel').'</a>';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
}
|
||||
|
||||
print '</div>';
|
||||
|
||||
} else {
|
||||
// Tree view
|
||||
if ($permissiontoadd) {
|
||||
print '<div style="margin-bottom:15px;">';
|
||||
print '<a class="button" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=create">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Load tree
|
||||
$tree = $anlage->fetchTree($id, $systemId);
|
||||
|
||||
if (!empty($tree)) {
|
||||
print '<div class="kundenkarte-tree" data-system="'.$systemId.'">';
|
||||
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs);
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<div class="opacitymedium">'.$langs->trans('NoInstallations').'</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print '</div>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
// Tooltip container
|
||||
print '<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
|
||||
/**
|
||||
* Print tree recursively
|
||||
*/
|
||||
function printTree($nodes, $socid, $systemId, $canEdit, $canDelete, $langs, $level = 0)
|
||||
{
|
||||
foreach ($nodes as $node) {
|
||||
$hasChildren = !empty($node->children);
|
||||
|
||||
// Build tooltip data for hover on icon
|
||||
$tooltipData = array(
|
||||
'label' => $node->label,
|
||||
'type' => $node->type_label,
|
||||
'location' => $node->location,
|
||||
'manufacturer' => $node->manufacturer,
|
||||
'model' => $node->model,
|
||||
'serial_number' => $node->serial_number,
|
||||
'power_rating' => $node->power_rating,
|
||||
'installation_date' => $node->installation_date ? dol_print_date($node->installation_date, 'day') : '',
|
||||
'note' => $node->note_private,
|
||||
);
|
||||
|
||||
print '<div class="kundenkarte-tree-node" style="margin-left:'.($level * 20).'px;">';
|
||||
print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">';
|
||||
|
||||
// Toggle
|
||||
if ($hasChildren) {
|
||||
print '<span class="kundenkarte-tree-toggle"><i class="fa fa-chevron-down"></i></span>';
|
||||
} else {
|
||||
print '<span class="kundenkarte-tree-toggle" style="visibility:hidden;"><i class="fa fa-chevron-down"></i></span>';
|
||||
}
|
||||
|
||||
// Icon with tooltip data
|
||||
$picto = $node->type_picto ? $node->type_picto : 'fa-cube';
|
||||
print '<span class="kundenkarte-tree-icon kundenkarte-tooltip-trigger" data-tooltip="'.htmlspecialchars(json_encode($tooltipData), ENT_QUOTES, 'UTF-8').'" data-anlage-id="'.$node->id.'">'.kundenkarte_render_icon($picto).'</span>';
|
||||
|
||||
// Label with manufacturer/power in parentheses + file indicators
|
||||
$viewUrl = $_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=view&anlage_id='.$node->id;
|
||||
print '<span class="kundenkarte-tree-label">'.dol_escape_htmltag($node->label);
|
||||
$labelInfo = array();
|
||||
if ($node->manufacturer) {
|
||||
$labelInfo[] = $node->manufacturer;
|
||||
}
|
||||
if ($node->power_rating) {
|
||||
$labelInfo[] = $node->power_rating;
|
||||
}
|
||||
if (!empty($labelInfo)) {
|
||||
print ' <span class="kundenkarte-tree-label-info">('.dol_escape_htmltag(implode(', ', $labelInfo)).')</span>';
|
||||
}
|
||||
// File indicators - directly after parentheses
|
||||
if ($node->image_count > 0 || $node->doc_count > 0) {
|
||||
print ' <span class="kundenkarte-tree-files">';
|
||||
if ($node->image_count > 0) {
|
||||
print '<a href="'.$viewUrl.'#files" class="kundenkarte-tree-file-badge kundenkarte-tree-file-images kundenkarte-images-trigger" data-anlage-id="'.$node->id.'" title="'.$node->image_count.' '.($node->image_count == 1 ? 'Bild' : 'Bilder').'">';
|
||||
print '<i class="fa fa-image"></i>';
|
||||
if ($node->image_count > 1) {
|
||||
print ' '.$node->image_count;
|
||||
}
|
||||
print '</a>';
|
||||
}
|
||||
if ($node->doc_count > 0) {
|
||||
print '<a href="'.$viewUrl.'#files" class="kundenkarte-tree-file-badge kundenkarte-tree-file-docs kundenkarte-docs-trigger" data-anlage-id="'.$node->id.'" title="'.$node->doc_count.' '.($node->doc_count == 1 ? 'Dokument' : 'Dokumente').'">';
|
||||
print '<i class="fa fa-file-pdf-o"></i>';
|
||||
if ($node->doc_count > 1) {
|
||||
print ' '.$node->doc_count;
|
||||
}
|
||||
print '</a>';
|
||||
}
|
||||
print '</span>';
|
||||
}
|
||||
print '</span>';
|
||||
|
||||
// Type badge
|
||||
if ($node->type_short || $node->type_label) {
|
||||
$typeDisplay = $node->type_short ? $node->type_short : $node->type_label;
|
||||
print '<span class="kundenkarte-tree-type badge badge-secondary">'.dol_escape_htmltag($typeDisplay).'</span>';
|
||||
}
|
||||
|
||||
// Location with icon
|
||||
if ($node->location) {
|
||||
print '<span class="kundenkarte-tree-location"><i class="fa fa-map-marker"></i> '.dol_escape_htmltag($node->location).'</span>';
|
||||
}
|
||||
|
||||
// Actions
|
||||
print '<span class="kundenkarte-tree-actions">';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||
if ($canEdit) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||
}
|
||||
if ($canDelete) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
||||
}
|
||||
print '</span>';
|
||||
|
||||
print '</div>';
|
||||
|
||||
// Children
|
||||
if ($hasChildren) {
|
||||
print '<div class="kundenkarte-tree-children">';
|
||||
printTree($node->children, $socid, $systemId, $canEdit, $canDelete, $langs, $level + 1);
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print tree options for select
|
||||
*/
|
||||
function printTreeOptions($nodes, $selected = 0, $excludeId = 0, $prefix = '')
|
||||
{
|
||||
foreach ($nodes as $node) {
|
||||
if ($node->id == $excludeId) continue;
|
||||
|
||||
$sel = ($node->id == $selected) ? ' selected' : '';
|
||||
print '<option value="'.$node->id.'"'.$sel.'>'.$prefix.dol_escape_htmltag($node->label).'</option>';
|
||||
|
||||
if (!empty($node->children)) {
|
||||
printTreeOptions($node->children, $selected, $excludeId, $prefix.' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
803
tabs/contact_anlagen.php
Normal file
803
tabs/contact_anlagen.php
Normal file
|
|
@ -0,0 +1,803 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file tabs/contact_anlagen.php
|
||||
* \brief Tab for technical installations on contact/address card
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/contact.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
|
||||
dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||
dol_include_once('/kundenkarte/class/anlagetype.class.php');
|
||||
dol_include_once('/kundenkarte/class/anlagefile.class.php');
|
||||
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
|
||||
|
||||
// Load translation files
|
||||
$langs->loadLangs(array('companies', 'kundenkarte@kundenkarte'));
|
||||
|
||||
// Get parameters
|
||||
$id = GETPOSTINT('id');
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
$systemId = GETPOSTINT('system');
|
||||
$anlageId = GETPOSTINT('anlage_id');
|
||||
$parentId = GETPOSTINT('parent_id');
|
||||
|
||||
// Security check
|
||||
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Initialize objects
|
||||
$object = new Contact($db);
|
||||
$form = new Form($db);
|
||||
$anlage = new Anlage($db);
|
||||
$anlageType = new AnlageType($db);
|
||||
|
||||
// Load contact
|
||||
if ($id > 0) {
|
||||
$result = $object->fetch($id);
|
||||
if ($result <= 0) {
|
||||
dol_print_error($db, $object->error);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$permissiontoread = $user->hasRight('kundenkarte', 'read');
|
||||
$permissiontoadd = $user->hasRight('kundenkarte', 'write');
|
||||
$permissiontodelete = $user->hasRight('kundenkarte', 'delete');
|
||||
|
||||
// Load ALL available systems (from dictionary)
|
||||
$allSystems = array();
|
||||
$sql = "SELECT rowid, code, label, picto, color FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system WHERE active = 1 ORDER BY position ASC";
|
||||
$resql = $db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$allSystems[$obj->rowid] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
// Load systems ENABLED for this contact
|
||||
$customerSystems = array();
|
||||
$sql = "SELECT ss.rowid, ss.fk_system, s.code, s.label, s.picto, s.color
|
||||
FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system ss
|
||||
JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system s ON s.rowid = ss.fk_system
|
||||
WHERE ss.fk_soc = ".((int) $object->socid)." AND ss.fk_contact = ".((int) $id)." AND ss.active = 1 AND s.active = 1
|
||||
ORDER BY s.position ASC";
|
||||
$resql = $db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$customerSystems[$obj->fk_system] = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to first enabled system if not specified
|
||||
if (empty($systemId) && !empty($customerSystems)) {
|
||||
$systemId = array_key_first($customerSystems);
|
||||
}
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
// Add system to contact
|
||||
if ($action == 'add_system' && $permissiontoadd) {
|
||||
$newSystemId = GETPOSTINT('new_system_id');
|
||||
if ($newSystemId > 0 && !isset($customerSystems[$newSystemId])) {
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX."kundenkarte_societe_system";
|
||||
$sql .= " (entity, fk_soc, fk_contact, fk_system, date_creation, fk_user_creat, active)";
|
||||
$sql .= " VALUES (".$conf->entity.", ".((int) $object->socid).", ".((int) $id).", ".((int) $newSystemId).", NOW(), ".((int) $user->id).", 1)";
|
||||
$result = $db->query($sql);
|
||||
if ($result) {
|
||||
setEventMessages($langs->trans('SystemAdded'), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$newSystemId);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($db->lasterror(), null, 'errors');
|
||||
}
|
||||
}
|
||||
$action = '';
|
||||
}
|
||||
|
||||
// Remove system from contact
|
||||
if ($action == 'confirm_remove_system' && $confirm == 'yes' && $permissiontodelete) {
|
||||
$removeSystemId = GETPOSTINT('remove_system_id');
|
||||
if ($removeSystemId > 0) {
|
||||
// Check if system has any elements for this contact
|
||||
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."kundenkarte_anlage WHERE fk_soc = ".((int) $object->socid)." AND fk_contact = ".((int) $id)." AND fk_system = ".((int) $removeSystemId);
|
||||
$resql = $db->query($sql);
|
||||
$obj = $db->fetch_object($resql);
|
||||
|
||||
if ($obj->cnt > 0) {
|
||||
setEventMessages($langs->trans('ErrorSystemHasElements'), null, 'errors');
|
||||
} else {
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system WHERE fk_soc = ".((int) $object->socid)." AND fk_contact = ".((int) $id)." AND fk_system = ".((int) $removeSystemId);
|
||||
$db->query($sql);
|
||||
setEventMessages($langs->trans('SystemRemoved'), null, 'mesgs');
|
||||
|
||||
// Switch to another system or none
|
||||
unset($customerSystems[$removeSystemId]);
|
||||
if (!empty($customerSystems)) {
|
||||
$systemId = array_key_first($customerSystems);
|
||||
} else {
|
||||
$systemId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.($systemId ? '&system='.$systemId : ''));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'add' && $permissiontoadd) {
|
||||
$anlage->label = GETPOST('label', 'alphanohtml');
|
||||
$anlage->fk_soc = $object->socid;
|
||||
$anlage->fk_contact = $id;
|
||||
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||
$anlage->fk_system = $systemId;
|
||||
$anlage->manufacturer = GETPOST('manufacturer', 'alphanohtml');
|
||||
$anlage->model = GETPOST('model', 'alphanohtml');
|
||||
$anlage->serial_number = GETPOST('serial_number', 'alphanohtml');
|
||||
$anlage->power_rating = GETPOST('power_rating', 'alphanohtml');
|
||||
$anlage->location = GETPOST('location', 'alphanohtml');
|
||||
$anlage->installation_date = dol_mktime(0, 0, 0, GETPOSTINT('installation_datemonth'), GETPOSTINT('installation_dateday'), GETPOSTINT('installation_dateyear'));
|
||||
$anlage->note_private = GETPOST('note_private', 'restricthtml');
|
||||
$anlage->status = 1;
|
||||
|
||||
// Get type for system ID
|
||||
$type = new AnlageType($db);
|
||||
if ($type->fetch($anlage->fk_anlage_type) > 0) {
|
||||
$anlage->fk_system = $type->fk_system;
|
||||
}
|
||||
|
||||
// Dynamic fields
|
||||
$fieldValues = array();
|
||||
$fields = $type->fetchFields();
|
||||
foreach ($fields as $field) {
|
||||
$value = GETPOST('field_'.$field->field_code, 'alphanohtml');
|
||||
if ($value !== '') {
|
||||
$fieldValues[$field->field_code] = $value;
|
||||
}
|
||||
}
|
||||
$anlage->setFieldValues($fieldValues);
|
||||
|
||||
$result = $anlage->create($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$anlage->fk_system);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||
$action = 'create';
|
||||
}
|
||||
}
|
||||
|
||||
if ($action == 'update' && $permissiontoadd) {
|
||||
$anlage->fetch($anlageId);
|
||||
$anlage->label = GETPOST('label', 'alphanohtml');
|
||||
$anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type');
|
||||
$anlage->fk_parent = GETPOSTINT('fk_parent');
|
||||
$anlage->manufacturer = GETPOST('manufacturer', 'alphanohtml');
|
||||
$anlage->model = GETPOST('model', 'alphanohtml');
|
||||
$anlage->serial_number = GETPOST('serial_number', 'alphanohtml');
|
||||
$anlage->power_rating = GETPOST('power_rating', 'alphanohtml');
|
||||
$anlage->location = GETPOST('location', 'alphanohtml');
|
||||
$anlage->installation_date = dol_mktime(0, 0, 0, GETPOSTINT('installation_datemonth'), GETPOSTINT('installation_dateday'), GETPOSTINT('installation_dateyear'));
|
||||
$anlage->note_private = GETPOST('note_private', 'restricthtml');
|
||||
|
||||
// Get type for system ID
|
||||
$type = new AnlageType($db);
|
||||
if ($type->fetch($anlage->fk_anlage_type) > 0) {
|
||||
$anlage->fk_system = $type->fk_system;
|
||||
}
|
||||
|
||||
// Dynamic fields
|
||||
$fieldValues = array();
|
||||
$fields = $type->fetchFields();
|
||||
foreach ($fields as $field) {
|
||||
$value = GETPOST('field_'.$field->field_code, 'alphanohtml');
|
||||
if ($value !== '') {
|
||||
$fieldValues[$field->field_code] = $value;
|
||||
}
|
||||
}
|
||||
$anlage->setFieldValues($fieldValues);
|
||||
|
||||
$result = $anlage->update($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$anlage->fk_system);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||
$action = 'edit';
|
||||
}
|
||||
}
|
||||
|
||||
if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodelete) {
|
||||
$anlage->fetch($anlageId);
|
||||
$systemIdBefore = $anlage->fk_system;
|
||||
$result = $anlage->delete($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($anlage->error, $anlage->errors, 'errors');
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemIdBefore);
|
||||
exit;
|
||||
}
|
||||
|
||||
// File upload
|
||||
if ($action == 'uploadfile' && $permissiontoadd) {
|
||||
$anlage->fetch($anlageId);
|
||||
$upload_dir = $anlage->getFileDirectory();
|
||||
|
||||
// Create directory if not exists
|
||||
if (!is_dir($upload_dir)) {
|
||||
dol_mkdir($upload_dir);
|
||||
}
|
||||
|
||||
if (!empty($_FILES['userfile']['name'])) {
|
||||
$result = dol_add_file_process($upload_dir, 0, 1, 'userfile', '', null, '', 1);
|
||||
if ($result > 0) {
|
||||
// Add to database
|
||||
$anlagefile = new AnlageFile($db);
|
||||
$anlagefile->fk_anlage = $anlageId;
|
||||
$anlagefile->filename = dol_sanitizeFileName($_FILES['userfile']['name']);
|
||||
// IMPORTANT: Store ONLY relative path (anlagen/socid/anlageid/filename) - never full path!
|
||||
$anlagefile->filepath = 'anlagen/'.$anlage->fk_soc.'/'.$anlage->id.'/'.$anlagefile->filename;
|
||||
$anlagefile->filesize = $_FILES['userfile']['size'];
|
||||
$anlagefile->mimetype = dol_mimetype($anlagefile->filename);
|
||||
$anlagefile->create($user);
|
||||
|
||||
// Generate thumbnail
|
||||
$anlagefile->generateThumbnail();
|
||||
|
||||
setEventMessages($langs->trans('FileUploaded'), null, 'mesgs');
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=view&anlage_id='.$anlageId);
|
||||
exit;
|
||||
}
|
||||
|
||||
// File delete (after confirmation)
|
||||
if ($action == 'confirm_deletefile' && $confirm == 'yes' && $permissiontodelete) {
|
||||
$fileId = GETPOSTINT('fileid');
|
||||
if ($fileId > 0) {
|
||||
$anlagefile = new AnlageFile($db);
|
||||
if ($anlagefile->fetch($fileId) > 0) {
|
||||
// Delete method handles physical file and database
|
||||
if ($anlagefile->delete($user) > 0) {
|
||||
setEventMessages($langs->trans('FileDeleted'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($langs->trans('Error'), null, 'errors');
|
||||
}
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=view&anlage_id='.$anlageId);
|
||||
exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs);
|
||||
llxHeader('', $title, '', '', 0, 0, array('/kundenkarte/js/kundenkarte.js'), array('/kundenkarte/css/kundenkarte.css'));
|
||||
|
||||
// Prepare tabs
|
||||
$head = contact_prepare_head($object);
|
||||
|
||||
print dol_get_fiche_head($head, 'anlagen', $langs->trans("ContactAddress"), -1, 'contact');
|
||||
|
||||
// Contact card
|
||||
$linkback = '<a href="'.DOL_URL_ROOT.'/contact/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
|
||||
dol_banner_tab($object, 'id', $linkback, 1, 'rowid', 'nom');
|
||||
|
||||
print '<div class="fichecenter">';
|
||||
|
||||
// Confirmation dialogs
|
||||
if ($action == 'delete') {
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?id='.$id.'&anlage_id='.$anlageId.'&system='.$systemId,
|
||||
$langs->trans('DeleteElement'),
|
||||
$langs->trans('ConfirmDeleteElement'),
|
||||
'confirm_delete',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
if ($action == 'remove_system') {
|
||||
$removeSystemId = GETPOSTINT('remove_system_id');
|
||||
$sysLabel = isset($customerSystems[$removeSystemId]) ? $customerSystems[$removeSystemId]->label : '';
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?id='.$id.'&remove_system_id='.$removeSystemId,
|
||||
$langs->trans('RemoveSystem'),
|
||||
$langs->trans('ConfirmRemoveSystem', $sysLabel),
|
||||
'confirm_remove_system',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
if ($action == 'askdeletefile') {
|
||||
$fileId = GETPOSTINT('fileid');
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&anlage_id='.$anlageId.'&fileid='.$fileId,
|
||||
$langs->trans('Delete'),
|
||||
$langs->trans('ConfirmDeleteFile'),
|
||||
'confirm_deletefile',
|
||||
'',
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
// System tabs (only show enabled systems for this contact)
|
||||
print '<div class="kundenkarte-system-tabs-wrapper">';
|
||||
print '<div class="kundenkarte-system-tabs">';
|
||||
foreach ($customerSystems as $sysId => $sys) {
|
||||
$activeClass = ($sysId == $systemId) ? ' active' : '';
|
||||
print '<div class="kundenkarte-system-tab'.$activeClass.'" data-system="'.$sysId.'">';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$sysId.'" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:8px;">';
|
||||
if ($sys->picto) {
|
||||
print '<span class="kundenkarte-system-tab-icon" style="color:'.$sys->color.';">'.kundenkarte_render_icon($sys->picto).'</span>';
|
||||
}
|
||||
print '<span>'.dol_escape_htmltag($sys->label).'</span>';
|
||||
print '</a>';
|
||||
// Remove button (only if no elements)
|
||||
if ($permissiontodelete && $sysId == $systemId) {
|
||||
print ' <a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=remove_system&remove_system_id='.$sysId.'" class="kundenkarte-system-remove" title="'.$langs->trans('RemoveSystem').'"><i class="fa fa-times"></i></a>';
|
||||
}
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Add system button (always on the right)
|
||||
if ($permissiontoadd) {
|
||||
// Get systems not yet enabled for this contact
|
||||
$availableSystems = array_diff_key($allSystems, $customerSystems);
|
||||
if (!empty($availableSystems)) {
|
||||
print '<button type="button" class="button small" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';" style="margin-left:auto;">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
|
||||
print '</button>';
|
||||
}
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
// Expand/Collapse buttons (only in tree view, not in create/edit/view)
|
||||
$isTreeView = !in_array($action, array('create', 'edit', 'view'));
|
||||
if ($isTreeView) {
|
||||
print '<div class="kundenkarte-tree-controls">';
|
||||
print '<button type="button" class="button small" id="btn-expand-all" title="'.$langs->trans('ExpandAll').'">';
|
||||
print '<i class="fa fa-expand"></i> '.$langs->trans('ExpandAll');
|
||||
print '</button>';
|
||||
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
|
||||
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
|
||||
print '</button>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print '</div>'; // End kundenkarte-system-tabs-wrapper
|
||||
|
||||
// Add system form (hidden by default)
|
||||
if ($permissiontoadd && !empty($availableSystems)) {
|
||||
print '<div id="add-system-form" class="kundenkarte-add-system-form" style="display:none;margin-bottom:15px;padding:10px;background:#f9f9f9;border-radius:6px;">';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="add_system">';
|
||||
print '<strong>'.$langs->trans('SelectSystemToAdd').':</strong> ';
|
||||
print '<select name="new_system_id" class="flat">';
|
||||
print '<option value="">'.$langs->trans('Select').'</option>';
|
||||
foreach ($availableSystems as $avSys) {
|
||||
print '<option value="'.$avSys->rowid.'">'.dol_escape_htmltag($avSys->label).'</option>';
|
||||
}
|
||||
print '</select>';
|
||||
print ' <button type="submit" class="button small">'.$langs->trans('Add').'</button>';
|
||||
print ' <button type="button" class="button small button-cancel" onclick="document.getElementById(\'add-system-form\').style.display=\'none\';">'.$langs->trans('Cancel').'</button>';
|
||||
print '</form>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Check if contact has any systems
|
||||
if (empty($customerSystems)) {
|
||||
print '<div class="opacitymedium" style="padding:20px;text-align:center;">';
|
||||
print '<i class="fa fa-info-circle" style="font-size:24px;margin-bottom:10px;"></i><br>';
|
||||
print $langs->trans('NoSystemsConfigured').'<br><br>';
|
||||
if ($permissiontoadd && !empty($allSystems)) {
|
||||
print $langs->trans('ClickAddSystemToStart');
|
||||
} else {
|
||||
print $langs->trans('ContactAdminToAddSystems');
|
||||
}
|
||||
print '</div>';
|
||||
} elseif ($systemId > 0) {
|
||||
// Show form or tree for selected system
|
||||
if (in_array($action, array('create', 'edit', 'view'))) {
|
||||
// Load element for edit/view
|
||||
if ($action != 'create' && $anlageId > 0) {
|
||||
$anlage->fetch($anlageId);
|
||||
$type = new AnlageType($db);
|
||||
$type->fetch($anlage->fk_anlage_type);
|
||||
$type->fetchFields();
|
||||
}
|
||||
|
||||
// Load types for select
|
||||
$types = $anlageType->fetchAllBySystem($systemId);
|
||||
|
||||
print '<div class="kundenkarte-element-form">';
|
||||
|
||||
if ($action == 'view') {
|
||||
// View mode
|
||||
print '<h3>'.dol_escape_htmltag($anlage->label).'</h3>';
|
||||
|
||||
print '<table class="border centpercent">';
|
||||
|
||||
print '<tr><td class="titlefield">'.$langs->trans('Type').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->type_label).'</td></tr>';
|
||||
|
||||
if ($anlage->manufacturer) {
|
||||
print '<tr><td>'.$langs->trans('FieldManufacturer').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->manufacturer).'</td></tr>';
|
||||
}
|
||||
if ($anlage->model) {
|
||||
print '<tr><td>'.$langs->trans('FieldModel').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->model).'</td></tr>';
|
||||
}
|
||||
if ($anlage->serial_number) {
|
||||
print '<tr><td>'.$langs->trans('FieldSerialNumber').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->serial_number).'</td></tr>';
|
||||
}
|
||||
if ($anlage->power_rating) {
|
||||
print '<tr><td>'.$langs->trans('FieldPowerRating').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->power_rating).'</td></tr>';
|
||||
}
|
||||
if ($anlage->location) {
|
||||
print '<tr><td>'.$langs->trans('FieldLocation').'</td>';
|
||||
print '<td>'.dol_escape_htmltag($anlage->location).'</td></tr>';
|
||||
}
|
||||
if ($anlage->installation_date) {
|
||||
print '<tr><td>'.$langs->trans('FieldInstallationDate').'</td>';
|
||||
print '<td>'.dol_print_date($anlage->installation_date, 'day').'</td></tr>';
|
||||
}
|
||||
|
||||
// Dynamic fields
|
||||
$fieldValues = $anlage->getFieldValues();
|
||||
foreach ($type->fields as $field) {
|
||||
if (isset($fieldValues[$field->field_code]) && $fieldValues[$field->field_code] !== '') {
|
||||
print '<tr><td>'.dol_escape_htmltag($field->field_label).'</td>';
|
||||
$value = $fieldValues[$field->field_code];
|
||||
// For select fields, show the label
|
||||
if ($field->field_type == 'select' && $field->field_options) {
|
||||
$options = json_decode($field->field_options, true);
|
||||
if (isset($options['options'][$value])) {
|
||||
$value = $options['options'][$value];
|
||||
}
|
||||
}
|
||||
print '<td>'.dol_escape_htmltag($value).'</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($anlage->note_private) {
|
||||
print '<tr><td>'.$langs->trans('FieldNotes').'</td>';
|
||||
print '<td>'.dol_htmlentitiesbr($anlage->note_private).'</td></tr>';
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Files section
|
||||
$anlagefile = new AnlageFile($db);
|
||||
$files = $anlagefile->fetchAllByAnlage($anlageId);
|
||||
|
||||
print '<br><h4>'.$langs->trans('AttachedFiles').'</h4>';
|
||||
|
||||
if ($permissiontoadd) {
|
||||
print '<form method="POST" enctype="multipart/form-data" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=uploadfile&anlage_id='.$anlageId.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="file" name="userfile" accept="image/*,.pdf,.doc,.docx">';
|
||||
print ' <button type="submit" class="button">'.$langs->trans('Upload').'</button>';
|
||||
print '</form><br>';
|
||||
}
|
||||
|
||||
if (!empty($files)) {
|
||||
print '<div class="kundenkarte-files-grid">';
|
||||
foreach ($files as $file) {
|
||||
print '<div class="kundenkarte-file-item">';
|
||||
print '<div class="kundenkarte-file-preview">';
|
||||
if ($file->file_type == 'image') {
|
||||
$thumbUrl = $file->getThumbUrl();
|
||||
if ($thumbUrl) {
|
||||
print '<img src="'.$thumbUrl.'" alt="">';
|
||||
} else {
|
||||
print '<img src="'.$file->getUrl().'" alt="" style="max-width:100%;max-height:100%;">';
|
||||
}
|
||||
} elseif ($file->file_type == 'pdf') {
|
||||
// PDF preview using iframe - 50% smaller, no toolbar
|
||||
print '<div class="kundenkarte-pdf-preview-wrapper">';
|
||||
print '<iframe src="'.$file->getUrl().'#page=1&toolbar=0&navpanes=0&statusbar=0&view=FitH" class="kundenkarte-pdf-preview-frame"></iframe>';
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<i class="fa fa-file-o" style="font-size:48px;color:#999;"></i>';
|
||||
}
|
||||
print '</div>';
|
||||
print '<div class="kundenkarte-file-info">';
|
||||
print '<div class="kundenkarte-file-name" title="'.dol_escape_htmltag($file->filename).'">'.dol_escape_htmltag(dol_trunc($file->filename, 20)).'</div>';
|
||||
print '<div class="kundenkarte-file-size">'.dol_print_size($file->filesize).'</div>';
|
||||
print '<div class="kundenkarte-file-actions">';
|
||||
print '<a href="'.$file->getUrl().'" target="_blank" class="kundenkarte-file-btn" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||
if ($permissiontodelete) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=askdeletefile&anlage_id='.$anlageId.'&fileid='.$file->id.'" class="kundenkarte-file-btn kundenkarte-file-btn-delete" title="'.$langs->trans('Delete').'"><i class="fa fa-trash"></i></a>';
|
||||
}
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
}
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<p class="opacitymedium">'.$langs->trans('NoFiles').'</p>';
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
print '<div class="tabsAction">';
|
||||
if ($permissiontoadd) {
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=edit&anlage_id='.$anlageId.'">'.$langs->trans('Modify').'</a>';
|
||||
}
|
||||
if ($permissiontodelete) {
|
||||
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=delete&anlage_id='.$anlageId.'">'.$langs->trans('Delete').'</a>';
|
||||
}
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'">'.$langs->trans('Back').'</a>';
|
||||
print '</div>';
|
||||
|
||||
} else {
|
||||
// Create/Edit form
|
||||
$formAction = ($action == 'create') ? 'add' : 'update';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="'.$formAction.'">';
|
||||
if ($action == 'edit') {
|
||||
print '<input type="hidden" name="anlage_id" value="'.$anlageId.'">';
|
||||
}
|
||||
|
||||
print '<table class="border centpercent">';
|
||||
|
||||
// Label
|
||||
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('Label').'</td>';
|
||||
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->label : GETPOST('label')).'" required></td></tr>';
|
||||
|
||||
// Type
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans('Type').'</td>';
|
||||
print '<td><select name="fk_anlage_type" class="flat minwidth200" id="select_type" required>';
|
||||
print '<option value="">'.$langs->trans('SelectType').'</option>';
|
||||
foreach ($types as $t) {
|
||||
$selected = ($action == 'edit' && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||
print '<option value="'.$t->id.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||
}
|
||||
print '</select>';
|
||||
if (empty($types)) {
|
||||
print '<br><span class="warning">'.$langs->trans('NoTypesDefinedForSystem').'</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
// Parent (uses contact-specific tree)
|
||||
$tree = $anlage->fetchTreeByContact($object->socid, $id, $systemId);
|
||||
print '<tr><td>'.$langs->trans('SelectParent').'</td>';
|
||||
print '<td><select name="fk_parent" class="flat minwidth200">';
|
||||
print '<option value="0">('.$langs->trans('Root').')</option>';
|
||||
printTreeOptions($tree, $action == 'edit' ? $anlage->fk_parent : $parentId, $action == 'edit' ? $anlageId : 0);
|
||||
print '</select></td></tr>';
|
||||
|
||||
// Manufacturer
|
||||
print '<tr><td>'.$langs->trans('FieldManufacturer').'</td>';
|
||||
print '<td><input type="text" name="manufacturer" class="flat minwidth200" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->manufacturer : GETPOST('manufacturer')).'"></td></tr>';
|
||||
|
||||
// Model
|
||||
print '<tr><td>'.$langs->trans('FieldModel').'</td>';
|
||||
print '<td><input type="text" name="model" class="flat minwidth200" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->model : GETPOST('model')).'"></td></tr>';
|
||||
|
||||
// Serial number
|
||||
print '<tr><td>'.$langs->trans('FieldSerialNumber').'</td>';
|
||||
print '<td><input type="text" name="serial_number" class="flat minwidth200" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->serial_number : GETPOST('serial_number')).'"></td></tr>';
|
||||
|
||||
// Power rating
|
||||
print '<tr><td>'.$langs->trans('FieldPowerRating').'</td>';
|
||||
print '<td><input type="text" name="power_rating" class="flat minwidth200" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->power_rating : GETPOST('power_rating')).'"></td></tr>';
|
||||
|
||||
// Location
|
||||
print '<tr><td>'.$langs->trans('FieldLocation').'</td>';
|
||||
print '<td><input type="text" name="location" class="flat minwidth300" value="'.dol_escape_htmltag($action == 'edit' ? $anlage->location : GETPOST('location')).'"></td></tr>';
|
||||
|
||||
// Installation date
|
||||
print '<tr><td>'.$langs->trans('FieldInstallationDate').'</td>';
|
||||
print '<td>'.$form->selectDate($action == 'edit' ? $anlage->installation_date : '', 'installation_date', 0, 0, 1, '', 1, 1).'</td></tr>';
|
||||
|
||||
// Notes
|
||||
print '<tr><td>'.$langs->trans('FieldNotes').'</td>';
|
||||
print '<td><textarea name="note_private" class="flat minwidth300" rows="3">'.dol_escape_htmltag($action == 'edit' ? $anlage->note_private : GETPOST('note_private')).'</textarea></td></tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Dynamic fields will be loaded via JavaScript based on type selection
|
||||
print '<div id="dynamic_fields"></div>';
|
||||
|
||||
print '<div class="center" style="margin-top:20px;">';
|
||||
print '<button type="submit" class="button">'.$langs->trans('Save').'</button>';
|
||||
print ' <a class="button button-cancel" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'">'.$langs->trans('Cancel').'</a>';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
}
|
||||
|
||||
print '</div>';
|
||||
|
||||
} else {
|
||||
// Tree view
|
||||
if ($permissiontoadd) {
|
||||
print '<div style="margin-bottom:15px;">';
|
||||
print '<a class="button" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=create">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Load tree for this contact
|
||||
$tree = $anlage->fetchTreeByContact($object->socid, $id, $systemId);
|
||||
|
||||
if (!empty($tree)) {
|
||||
print '<div class="kundenkarte-tree" data-system="'.$systemId.'">';
|
||||
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs);
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<div class="opacitymedium">'.$langs->trans('NoInstallations').'</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print '</div>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
// Tooltip container
|
||||
print '<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
|
||||
/**
|
||||
* Print tree recursively
|
||||
*/
|
||||
function printTree($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs, $level = 0)
|
||||
{
|
||||
foreach ($nodes as $node) {
|
||||
$hasChildren = !empty($node->children);
|
||||
|
||||
// Build tooltip data for hover on icon
|
||||
$tooltipData = array(
|
||||
'label' => $node->label,
|
||||
'type' => $node->type_label,
|
||||
'location' => $node->location,
|
||||
'manufacturer' => $node->manufacturer,
|
||||
'model' => $node->model,
|
||||
'serial_number' => $node->serial_number,
|
||||
'power_rating' => $node->power_rating,
|
||||
'installation_date' => $node->installation_date ? dol_print_date($node->installation_date, 'day') : '',
|
||||
'note' => $node->note_private,
|
||||
);
|
||||
|
||||
print '<div class="kundenkarte-tree-node" style="margin-left:'.($level * 20).'px;">';
|
||||
print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">';
|
||||
|
||||
// Toggle
|
||||
if ($hasChildren) {
|
||||
print '<span class="kundenkarte-tree-toggle"><i class="fa fa-chevron-down"></i></span>';
|
||||
} else {
|
||||
print '<span class="kundenkarte-tree-toggle" style="visibility:hidden;"><i class="fa fa-chevron-down"></i></span>';
|
||||
}
|
||||
|
||||
// Icon with tooltip data
|
||||
$picto = $node->type_picto ? $node->type_picto : 'fa-cube';
|
||||
print '<span class="kundenkarte-tree-icon kundenkarte-tooltip-trigger" data-tooltip="'.htmlspecialchars(json_encode($tooltipData), ENT_QUOTES, 'UTF-8').'" data-anlage-id="'.$node->id.'">'.kundenkarte_render_icon($picto).'</span>';
|
||||
|
||||
// Label with manufacturer/power in parentheses + file indicators
|
||||
$viewUrl = $_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=view&anlage_id='.$node->id;
|
||||
print '<span class="kundenkarte-tree-label">'.dol_escape_htmltag($node->label);
|
||||
$labelInfo = array();
|
||||
if ($node->manufacturer) {
|
||||
$labelInfo[] = $node->manufacturer;
|
||||
}
|
||||
if ($node->power_rating) {
|
||||
$labelInfo[] = $node->power_rating;
|
||||
}
|
||||
if (!empty($labelInfo)) {
|
||||
print ' <span class="kundenkarte-tree-label-info">('.dol_escape_htmltag(implode(', ', $labelInfo)).')</span>';
|
||||
}
|
||||
// File indicators - directly after parentheses
|
||||
if ($node->image_count > 0 || $node->doc_count > 0) {
|
||||
print ' <span class="kundenkarte-tree-files">';
|
||||
if ($node->image_count > 0) {
|
||||
print '<a href="'.$viewUrl.'#files" class="kundenkarte-tree-file-badge kundenkarte-tree-file-images kundenkarte-images-trigger" data-anlage-id="'.$node->id.'" title="'.$node->image_count.' '.($node->image_count == 1 ? 'Bild' : 'Bilder').'">';
|
||||
print '<i class="fa fa-image"></i>';
|
||||
if ($node->image_count > 1) {
|
||||
print ' '.$node->image_count;
|
||||
}
|
||||
print '</a>';
|
||||
}
|
||||
if ($node->doc_count > 0) {
|
||||
print '<a href="'.$viewUrl.'#files" class="kundenkarte-tree-file-badge kundenkarte-tree-file-docs kundenkarte-docs-trigger" data-anlage-id="'.$node->id.'" title="'.$node->doc_count.' '.($node->doc_count == 1 ? 'Dokument' : 'Dokumente').'">';
|
||||
print '<i class="fa fa-file-pdf-o"></i>';
|
||||
if ($node->doc_count > 1) {
|
||||
print ' '.$node->doc_count;
|
||||
}
|
||||
print '</a>';
|
||||
}
|
||||
print '</span>';
|
||||
}
|
||||
print '</span>';
|
||||
|
||||
// Type badge
|
||||
if ($node->type_short || $node->type_label) {
|
||||
$typeDisplay = $node->type_short ? $node->type_short : $node->type_label;
|
||||
print '<span class="kundenkarte-tree-type badge badge-secondary">'.dol_escape_htmltag($typeDisplay).'</span>';
|
||||
}
|
||||
|
||||
// Location with icon
|
||||
if ($node->location) {
|
||||
print '<span class="kundenkarte-tree-location"><i class="fa fa-map-marker"></i> '.dol_escape_htmltag($node->location).'</span>';
|
||||
}
|
||||
|
||||
// Actions
|
||||
print '<span class="kundenkarte-tree-actions">';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||
if ($canEdit) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||
}
|
||||
if ($canDelete) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
|
||||
}
|
||||
print '</span>';
|
||||
|
||||
print '</div>';
|
||||
|
||||
// Children
|
||||
if ($hasChildren) {
|
||||
print '<div class="kundenkarte-tree-children">';
|
||||
printTree($node->children, $contactid, $systemId, $canEdit, $canDelete, $langs, $level + 1);
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print tree options for select
|
||||
*/
|
||||
function printTreeOptions($nodes, $selected = 0, $excludeId = 0, $prefix = '')
|
||||
{
|
||||
foreach ($nodes as $node) {
|
||||
if ($node->id == $excludeId) continue;
|
||||
|
||||
$sel = ($node->id == $selected) ? ' selected' : '';
|
||||
print '<option value="'.$node->id.'"'.$sel.'>'.$prefix.dol_escape_htmltag($node->label).'</option>';
|
||||
|
||||
if (!empty($node->children)) {
|
||||
printTreeOptions($node->children, $selected, $excludeId, $prefix.' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
363
tabs/contact_favoriteproducts.php
Normal file
363
tabs/contact_favoriteproducts.php
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file tabs/contact_favoriteproducts.php
|
||||
* \brief Tab for favorite products on contact/address card
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/contact.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
||||
dol_include_once('/kundenkarte/class/favoriteproduct.class.php');
|
||||
|
||||
// Load translation files
|
||||
$langs->loadLangs(array('companies', 'products', 'orders', 'kundenkarte@kundenkarte'));
|
||||
|
||||
// Get parameters
|
||||
$id = GETPOSTINT('id');
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
|
||||
// Security check
|
||||
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Initialize objects
|
||||
$object = new Contact($db);
|
||||
$form = new Form($db);
|
||||
$favoriteProduct = new FavoriteProduct($db);
|
||||
|
||||
// Load contact
|
||||
if ($id > 0) {
|
||||
$result = $object->fetch($id);
|
||||
if ($result <= 0) {
|
||||
dol_print_error($db, $object->error);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$permissiontoread = $user->hasRight('kundenkarte', 'read');
|
||||
$permissiontoadd = $user->hasRight('kundenkarte', 'write');
|
||||
$permissiontodelete = $user->hasRight('kundenkarte', 'delete');
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
if ($action == 'add' && $permissiontoadd) {
|
||||
$productid = GETPOSTINT('productid');
|
||||
$qty = GETPOSTFLOAT('qty');
|
||||
|
||||
if ($productid > 0) {
|
||||
$favoriteProduct->fk_soc = $object->socid;
|
||||
$favoriteProduct->fk_contact = $id;
|
||||
$favoriteProduct->fk_product = $productid;
|
||||
$favoriteProduct->qty = $qty > 0 ? $qty : 1;
|
||||
$favoriteProduct->active = 1;
|
||||
|
||||
$result = $favoriteProduct->create($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($favoriteProduct->error, $favoriteProduct->errors, 'errors');
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'delete' && $permissiontodelete) {
|
||||
$favid = GETPOSTINT('favid');
|
||||
if ($favid > 0) {
|
||||
$favoriteProduct->fetch($favid);
|
||||
$result = $favoriteProduct->delete($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($favoriteProduct->error, $favoriteProduct->errors, 'errors');
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'updateqty' && $permissiontoadd) {
|
||||
$favid = GETPOSTINT('favid');
|
||||
$qty = GETPOSTFLOAT('qty');
|
||||
|
||||
if ($favid > 0 && $qty > 0) {
|
||||
$favoriteProduct->fetch($favid);
|
||||
$favoriteProduct->qty = $qty;
|
||||
$result = $favoriteProduct->update($user);
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'moveup' && $permissiontoadd) {
|
||||
$favid = GETPOSTINT('favid');
|
||||
if ($favid > 0) {
|
||||
$favoriteProduct->moveUpByContact($favid, $id);
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'movedown' && $permissiontoadd) {
|
||||
$favid = GETPOSTINT('favid');
|
||||
if ($favid > 0) {
|
||||
$favoriteProduct->moveDownByContact($favid, $id);
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'confirm_generateorder' && $confirm == 'yes' && $permissiontoadd) {
|
||||
$selected = GETPOST('selected_products', 'array');
|
||||
$quantities = GETPOST('quantities', 'array');
|
||||
|
||||
if (!empty($selected)) {
|
||||
$result = $favoriteProduct->generateOrderByContact($user, $object->socid, $id, $selected, $quantities);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('OrderGenerated', $result), null, 'mesgs');
|
||||
header('Location: '.DOL_URL_ROOT.'/commande/card.php?id='.$result);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($favoriteProduct->error, $favoriteProduct->errors, 'errors');
|
||||
}
|
||||
} else {
|
||||
setEventMessages($langs->trans('NoProductsSelected'), null, 'warnings');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$title = $langs->trans('FavoriteProducts').' - '.$object->getFullName($langs);
|
||||
llxHeader('', $title);
|
||||
|
||||
// Fetch favorites for this contact
|
||||
$favorites = $favoriteProduct->fetchAllByContact($id);
|
||||
|
||||
// Prepare tabs
|
||||
$head = contact_prepare_head($object);
|
||||
|
||||
print dol_get_fiche_head($head, 'favoriteproducts', $langs->trans("ContactAddress"), -1, 'contact');
|
||||
|
||||
// Contact card
|
||||
$linkback = '<a href="'.DOL_URL_ROOT.'/contact/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
|
||||
|
||||
dol_banner_tab($object, 'id', $linkback, 1, 'rowid', 'nom');
|
||||
|
||||
print '<div class="fichecenter">';
|
||||
|
||||
// Confirmation dialog for order generation
|
||||
if ($action == 'generateorder') {
|
||||
$formquestion = array();
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?id='.$id,
|
||||
$langs->trans('GenerateOrder'),
|
||||
$langs->trans('ConfirmGenerateOrder'),
|
||||
'confirm_generateorder',
|
||||
$formquestion,
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
print '<div class="underbanner clearboth"></div>';
|
||||
|
||||
// Add product form
|
||||
if ($permissiontoadd) {
|
||||
print '<div class="kundenkarte-favorites-add">';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="add">';
|
||||
|
||||
print '<div class="product-search" style="flex:1;">';
|
||||
print $form->select_produits(0, 'productid', '', 0, 0, 1, 2, '', 0, array(), $object->socid, '1', 0, 'minwidth300', 0, '', -1, 1);
|
||||
print '</div>';
|
||||
|
||||
print '<div style="display:flex;align-items:center;gap:10px;">';
|
||||
print '<label for="qty">'.$langs->trans('Qty').':</label>';
|
||||
print '<input type="number" name="qty" id="qty" value="1" min="0.01" step="1" class="flat" style="width:80px;">';
|
||||
print '<button type="submit" class="button">'.$langs->trans('Add').'</button>';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// List of favorites
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'" id="form_favorites">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="confirm_generateorder">';
|
||||
print '<input type="hidden" name="confirm" value="yes">';
|
||||
|
||||
if (is_array($favorites) && count($favorites) > 0) {
|
||||
print '<table class="noborder centpercent kundenkarte-favorites-table">';
|
||||
print '<thead>';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th class="center" style="width:30px;">';
|
||||
print '<input type="checkbox" id="kundenkarte-select-all">';
|
||||
print '</th>';
|
||||
if ($permissiontoadd) {
|
||||
print '<th class="center" style="width:60px;">'.$langs->trans('Position').'</th>';
|
||||
}
|
||||
print '<th>'.$langs->trans('Product').'</th>';
|
||||
print '<th class="center" style="width:100px;">'.$langs->trans('DefaultQuantity').'</th>';
|
||||
print '<th class="right" style="width:100px;">'.$langs->trans('UnitPriceHT').'</th>';
|
||||
print '<th class="right" style="width:120px;">'.$langs->trans('Total').'</th>';
|
||||
print '<th class="center" style="width:80px;">'.$langs->trans('Actions').'</th>';
|
||||
print '</tr>';
|
||||
print '</thead>';
|
||||
print '<tbody>';
|
||||
|
||||
$totalHT = 0;
|
||||
$totalItems = count($favorites);
|
||||
$currentIndex = 0;
|
||||
foreach ($favorites as $fav) {
|
||||
$currentIndex++;
|
||||
$lineTotal = $fav->qty * $fav->product_price;
|
||||
$totalHT += $lineTotal;
|
||||
|
||||
// Format quantity
|
||||
$qtyDisplay = (floor($fav->qty) == $fav->qty) ? (int)$fav->qty : round($fav->qty, 2);
|
||||
|
||||
print '<tr class="oddeven" data-favorite-id="'.$fav->id.'">';
|
||||
|
||||
// Checkbox
|
||||
print '<td class="center">';
|
||||
print '<input type="checkbox" name="selected_products[]" value="'.$fav->id.'" checked>';
|
||||
print '</td>';
|
||||
|
||||
// Position (up/down buttons)
|
||||
if ($permissiontoadd) {
|
||||
print '<td class="center nowraponall">';
|
||||
if ($currentIndex > 1) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=moveup&favid='.$fav->id.'&token='.newToken().'" class="reposition" title="'.$langs->trans('Up').'">';
|
||||
print '<i class="fa fa-arrow-up"></i>';
|
||||
print '</a> ';
|
||||
} else {
|
||||
print '<span class="opacitymedium"><i class="fa fa-arrow-up"></i></span> ';
|
||||
}
|
||||
if ($currentIndex < $totalItems) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=movedown&favid='.$fav->id.'&token='.newToken().'" class="reposition" title="'.$langs->trans('Down').'">';
|
||||
print '<i class="fa fa-arrow-down"></i>';
|
||||
print '</a>';
|
||||
} else {
|
||||
print '<span class="opacitymedium"><i class="fa fa-arrow-down"></i></span>';
|
||||
}
|
||||
print '</td>';
|
||||
}
|
||||
|
||||
// Product
|
||||
print '<td>';
|
||||
print '<a href="'.DOL_URL_ROOT.'/product/card.php?id='.$fav->fk_product.'">';
|
||||
print img_object('', 'product', 'class="pictofixedwidth"');
|
||||
print dol_escape_htmltag($fav->product_ref).' - '.dol_escape_htmltag($fav->product_label);
|
||||
print '</a>';
|
||||
print '</td>';
|
||||
|
||||
// Quantity
|
||||
print '<td class="center nowraponall">';
|
||||
print '<div style="display:inline-flex;align-items:stretch;gap:0;">';
|
||||
print '<input type="text" name="quantities['.$fav->id.']" value="'.$qtyDisplay.'" class="flat kundenkarte-favorites-qty" style="width:60px;text-align:center;border-radius:4px 0 0 4px;border-right:none;" data-fav-id="'.$fav->id.'">';
|
||||
print '<button type="button" class="button kundenkarte-qty-save" data-fav-id="'.$fav->id.'" title="'.$langs->trans('Save').'" style="border-radius:0 4px 4px 0;padding:0 8px;margin:0;"><i class="fa fa-save"></i></button>';
|
||||
print '</div>';
|
||||
print '</td>';
|
||||
|
||||
// Unit price
|
||||
print '<td class="right">'.price($fav->product_price).'</td>';
|
||||
|
||||
// Total
|
||||
print '<td class="right">'.price($lineTotal).'</td>';
|
||||
|
||||
// Actions
|
||||
print '<td class="center nowraponall">';
|
||||
if ($permissiontodelete) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=delete&favid='.$fav->id.'&token='.newToken().'" class="deletelink">';
|
||||
print img_delete();
|
||||
print '</a>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
print '</tr>';
|
||||
}
|
||||
|
||||
print '</tbody>';
|
||||
print '<tfoot>';
|
||||
print '<tr class="liste_total">';
|
||||
$colspan = $permissiontoadd ? 5 : 4;
|
||||
print '<td colspan="'.$colspan.'" class="right"><strong>'.$langs->trans('Total').'</strong></td>';
|
||||
print '<td class="right"><strong>'.price($totalHT).'</strong></td>';
|
||||
print '<td></td>';
|
||||
print '</tr>';
|
||||
print '</tfoot>';
|
||||
print '</table>';
|
||||
|
||||
// Actions bar
|
||||
print '<div class="kundenkarte-favorites-actions">';
|
||||
print '<div>';
|
||||
print '<input type="checkbox" id="kundenkarte-select-all-bottom"> ';
|
||||
print '<label for="kundenkarte-select-all-bottom">'.$langs->trans('SelectAll').'</label>';
|
||||
print '</div>';
|
||||
|
||||
if ($permissiontoadd) {
|
||||
print '<button type="submit" class="button" id="btn-generate-order" data-text="'.$langs->trans('GenerateOrder').' (%d)" data-text-none="'.$langs->trans('GenerateOrder').'">';
|
||||
print $langs->trans('GenerateOrder');
|
||||
print '</button>';
|
||||
}
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<div class="opacitymedium">'.$langs->trans('NoFavoriteProducts').'</div>';
|
||||
}
|
||||
|
||||
print '</form>';
|
||||
print '</div>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
// JavaScript for checkbox handling
|
||||
print '<script>
|
||||
$(document).ready(function() {
|
||||
function updateSelectAll() {
|
||||
var total = $("input[name=\'selected_products[]\']").length;
|
||||
var checked = $("input[name=\'selected_products[]\']:checked").length;
|
||||
$("#kundenkarte-select-all, #kundenkarte-select-all-bottom").prop("checked", total > 0 && total == checked);
|
||||
}
|
||||
|
||||
$("#kundenkarte-select-all, #kundenkarte-select-all-bottom").on("change", function() {
|
||||
var isChecked = $(this).prop("checked");
|
||||
$("input[name=\'selected_products[]\']").prop("checked", isChecked);
|
||||
$("#kundenkarte-select-all, #kundenkarte-select-all-bottom").prop("checked", isChecked);
|
||||
});
|
||||
|
||||
$("input[name=\'selected_products[]\']").on("change", function() {
|
||||
updateSelectAll();
|
||||
});
|
||||
|
||||
updateSelectAll();
|
||||
});
|
||||
</script>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
363
tabs/favoriteproducts.php
Executable file
363
tabs/favoriteproducts.php
Executable file
|
|
@ -0,0 +1,363 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file tabs/favoriteproducts.php
|
||||
* \brief Tab for favorite products on thirdparty card
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
||||
dol_include_once('/kundenkarte/class/favoriteproduct.class.php');
|
||||
|
||||
// Load translation files
|
||||
$langs->loadLangs(array('companies', 'products', 'orders', 'kundenkarte@kundenkarte'));
|
||||
|
||||
// Get parameters
|
||||
$id = GETPOSTINT('id');
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
|
||||
// Security check
|
||||
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Initialize objects
|
||||
$object = new Societe($db);
|
||||
$form = new Form($db);
|
||||
$favoriteProduct = new FavoriteProduct($db);
|
||||
|
||||
// Load thirdparty
|
||||
if ($id > 0) {
|
||||
$result = $object->fetch($id);
|
||||
if ($result <= 0) {
|
||||
dol_print_error($db, $object->error);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$permissiontoread = $user->hasRight('kundenkarte', 'read');
|
||||
$permissiontoadd = $user->hasRight('kundenkarte', 'write');
|
||||
$permissiontodelete = $user->hasRight('kundenkarte', 'delete');
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
if ($action == 'add' && $permissiontoadd) {
|
||||
$productid = GETPOSTINT('productid');
|
||||
$qty = GETPOSTFLOAT('qty');
|
||||
|
||||
if ($productid > 0) {
|
||||
$favoriteProduct->fk_soc = $id;
|
||||
$favoriteProduct->fk_product = $productid;
|
||||
$favoriteProduct->qty = $qty > 0 ? $qty : 1;
|
||||
$favoriteProduct->active = 1;
|
||||
|
||||
$result = $favoriteProduct->create($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($favoriteProduct->error, $favoriteProduct->errors, 'errors');
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'delete' && $permissiontodelete) {
|
||||
$favid = GETPOSTINT('favid');
|
||||
if ($favid > 0) {
|
||||
$favoriteProduct->fetch($favid);
|
||||
$result = $favoriteProduct->delete($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages($favoriteProduct->error, $favoriteProduct->errors, 'errors');
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'updateqty' && $permissiontoadd) {
|
||||
$favid = GETPOSTINT('favid');
|
||||
$qty = GETPOSTFLOAT('qty');
|
||||
|
||||
if ($favid > 0 && $qty > 0) {
|
||||
$favoriteProduct->fetch($favid);
|
||||
$favoriteProduct->qty = $qty;
|
||||
$result = $favoriteProduct->update($user);
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'moveup' && $permissiontoadd) {
|
||||
$favid = GETPOSTINT('favid');
|
||||
if ($favid > 0) {
|
||||
$favoriteProduct->moveUp($favid, $id);
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'movedown' && $permissiontoadd) {
|
||||
$favid = GETPOSTINT('favid');
|
||||
if ($favid > 0) {
|
||||
$favoriteProduct->moveDown($favid, $id);
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'confirm_generateorder' && $confirm == 'yes' && $permissiontoadd) {
|
||||
$selected = GETPOST('selected_products', 'array');
|
||||
$quantities = GETPOST('quantities', 'array');
|
||||
|
||||
if (!empty($selected)) {
|
||||
$result = $favoriteProduct->generateOrder($user, $id, $selected, $quantities);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans('OrderGenerated', $result), null, 'mesgs');
|
||||
header('Location: '.DOL_URL_ROOT.'/commande/card.php?id='.$result);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($favoriteProduct->error, $favoriteProduct->errors, 'errors');
|
||||
}
|
||||
} else {
|
||||
setEventMessages($langs->trans('NoProductsSelected'), null, 'warnings');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$title = $langs->trans('FavoriteProducts').' - '.$object->name;
|
||||
llxHeader('', $title);
|
||||
|
||||
// Fetch favorites
|
||||
$favorites = $favoriteProduct->fetchAllBySociete($id);
|
||||
|
||||
// Prepare tabs
|
||||
$head = societe_prepare_head($object);
|
||||
|
||||
print dol_get_fiche_head($head, 'favoriteproducts', $langs->trans("ThirdParty"), -1, 'company');
|
||||
|
||||
// Thirdparty card
|
||||
$linkback = '<a href="'.DOL_URL_ROOT.'/societe/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
|
||||
|
||||
dol_banner_tab($object, 'socid', $linkback, ($user->socid ? 0 : 1), 'rowid', 'nom');
|
||||
|
||||
print '<div class="fichecenter">';
|
||||
|
||||
// Confirmation dialog for order generation
|
||||
if ($action == 'generateorder') {
|
||||
$formquestion = array();
|
||||
print $form->formconfirm(
|
||||
$_SERVER['PHP_SELF'].'?id='.$id,
|
||||
$langs->trans('GenerateOrder'),
|
||||
$langs->trans('ConfirmGenerateOrder'),
|
||||
'confirm_generateorder',
|
||||
$formquestion,
|
||||
'yes',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
print '<div class="underbanner clearboth"></div>';
|
||||
|
||||
// Add product form
|
||||
if ($permissiontoadd) {
|
||||
print '<div class="kundenkarte-favorites-add">';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="add">';
|
||||
|
||||
print '<div class="product-search" style="flex:1;">';
|
||||
print $form->select_produits(0, 'productid', '', 0, 0, 1, 2, '', 0, array(), $id, '1', 0, 'minwidth300', 0, '', -1, 1);
|
||||
print '</div>';
|
||||
|
||||
print '<div style="display:flex;align-items:center;gap:10px;">';
|
||||
print '<label for="qty">'.$langs->trans('Qty').':</label>';
|
||||
print '<input type="number" name="qty" id="qty" value="1" min="0.01" step="1" class="flat" style="width:80px;">';
|
||||
print '<button type="submit" class="button">'.$langs->trans('Add').'</button>';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// List of favorites
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$id.'" id="form_favorites">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="confirm_generateorder">';
|
||||
print '<input type="hidden" name="confirm" value="yes">';
|
||||
|
||||
if (is_array($favorites) && count($favorites) > 0) {
|
||||
print '<table class="noborder centpercent kundenkarte-favorites-table">';
|
||||
print '<thead>';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th class="center" style="width:30px;">';
|
||||
print '<input type="checkbox" id="kundenkarte-select-all">';
|
||||
print '</th>';
|
||||
if ($permissiontoadd) {
|
||||
print '<th class="center" style="width:60px;">'.$langs->trans('Position').'</th>';
|
||||
}
|
||||
print '<th>'.$langs->trans('Product').'</th>';
|
||||
print '<th class="center" style="width:100px;">'.$langs->trans('DefaultQuantity').'</th>';
|
||||
print '<th class="right" style="width:100px;">'.$langs->trans('UnitPriceHT').'</th>';
|
||||
print '<th class="right" style="width:120px;">'.$langs->trans('Total').'</th>';
|
||||
print '<th class="center" style="width:80px;">'.$langs->trans('Actions').'</th>';
|
||||
print '</tr>';
|
||||
print '</thead>';
|
||||
print '<tbody>';
|
||||
|
||||
$totalHT = 0;
|
||||
$totalItems = count($favorites);
|
||||
$currentIndex = 0;
|
||||
foreach ($favorites as $fav) {
|
||||
$currentIndex++;
|
||||
$lineTotal = $fav->qty * $fav->product_price;
|
||||
$totalHT += $lineTotal;
|
||||
|
||||
// Format quantity: show as integer if whole number, otherwise 2 decimals
|
||||
// Use '.' as decimal separator for HTML number input compatibility
|
||||
$qtyDisplay = (floor($fav->qty) == $fav->qty) ? (int)$fav->qty : round($fav->qty, 2);
|
||||
|
||||
print '<tr class="oddeven" data-favorite-id="'.$fav->id.'">';
|
||||
|
||||
// Checkbox
|
||||
print '<td class="center">';
|
||||
print '<input type="checkbox" name="selected_products[]" value="'.$fav->id.'" checked>';
|
||||
print '</td>';
|
||||
|
||||
// Position (up/down buttons)
|
||||
if ($permissiontoadd) {
|
||||
print '<td class="center nowraponall">';
|
||||
if ($currentIndex > 1) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=moveup&favid='.$fav->id.'&token='.newToken().'" class="reposition" title="'.$langs->trans('Up').'">';
|
||||
print '<i class="fa fa-arrow-up"></i>';
|
||||
print '</a> ';
|
||||
} else {
|
||||
print '<span class="opacitymedium"><i class="fa fa-arrow-up"></i></span> ';
|
||||
}
|
||||
if ($currentIndex < $totalItems) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=movedown&favid='.$fav->id.'&token='.newToken().'" class="reposition" title="'.$langs->trans('Down').'">';
|
||||
print '<i class="fa fa-arrow-down"></i>';
|
||||
print '</a>';
|
||||
} else {
|
||||
print '<span class="opacitymedium"><i class="fa fa-arrow-down"></i></span>';
|
||||
}
|
||||
print '</td>';
|
||||
}
|
||||
|
||||
// Product
|
||||
print '<td>';
|
||||
print '<a href="'.DOL_URL_ROOT.'/product/card.php?id='.$fav->fk_product.'">';
|
||||
print img_object('', 'product', 'class="pictofixedwidth"');
|
||||
print dol_escape_htmltag($fav->product_ref).' - '.dol_escape_htmltag($fav->product_label);
|
||||
print '</a>';
|
||||
print '</td>';
|
||||
|
||||
// Quantity
|
||||
print '<td class="center nowraponall">';
|
||||
print '<div style="display:inline-flex;align-items:stretch;gap:0;">';
|
||||
print '<input type="text" name="quantities['.$fav->id.']" value="'.$qtyDisplay.'" class="flat kundenkarte-favorites-qty" style="width:60px;text-align:center;border-radius:4px 0 0 4px;border-right:none;" data-fav-id="'.$fav->id.'">';
|
||||
print '<button type="button" class="button kundenkarte-qty-save" data-fav-id="'.$fav->id.'" title="'.$langs->trans('Save').'" style="border-radius:0 4px 4px 0;padding:0 8px;margin:0;"><i class="fa fa-save"></i></button>';
|
||||
print '</div>';
|
||||
print '</td>';
|
||||
|
||||
// Unit price
|
||||
print '<td class="right">'.price($fav->product_price).'</td>';
|
||||
|
||||
// Total
|
||||
print '<td class="right">'.price($lineTotal).'</td>';
|
||||
|
||||
// Actions
|
||||
print '<td class="center nowraponall">';
|
||||
if ($permissiontodelete) {
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=delete&favid='.$fav->id.'&token='.newToken().'" class="deletelink">';
|
||||
print img_delete();
|
||||
print '</a>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
print '</tr>';
|
||||
}
|
||||
|
||||
print '</tbody>';
|
||||
print '<tfoot>';
|
||||
print '<tr class="liste_total">';
|
||||
$colspan = $permissiontoadd ? 5 : 4;
|
||||
print '<td colspan="'.$colspan.'" class="right"><strong>'.$langs->trans('Total').'</strong></td>';
|
||||
print '<td class="right"><strong>'.price($totalHT).'</strong></td>';
|
||||
print '<td></td>';
|
||||
print '</tr>';
|
||||
print '</tfoot>';
|
||||
print '</table>';
|
||||
|
||||
// Actions bar
|
||||
print '<div class="kundenkarte-favorites-actions">';
|
||||
print '<div>';
|
||||
print '<input type="checkbox" id="kundenkarte-select-all-bottom"> ';
|
||||
print '<label for="kundenkarte-select-all-bottom">'.$langs->trans('SelectAll').'</label>';
|
||||
print '</div>';
|
||||
|
||||
if ($permissiontoadd) {
|
||||
print '<button type="submit" class="button" id="btn-generate-order" data-text="'.$langs->trans('GenerateOrder').' (%d)" data-text-none="'.$langs->trans('GenerateOrder').'">';
|
||||
print $langs->trans('GenerateOrder');
|
||||
print '</button>';
|
||||
}
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<div class="opacitymedium">'.$langs->trans('NoFavoriteProducts').'</div>';
|
||||
}
|
||||
|
||||
print '</form>';
|
||||
print '</div>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
// JavaScript for checkbox handling
|
||||
print '<script>
|
||||
$(document).ready(function() {
|
||||
function updateSelectAll() {
|
||||
var total = $("input[name=\'selected_products[]\']").length;
|
||||
var checked = $("input[name=\'selected_products[]\']:checked").length;
|
||||
$("#kundenkarte-select-all, #kundenkarte-select-all-bottom").prop("checked", total > 0 && total == checked);
|
||||
}
|
||||
|
||||
$("#kundenkarte-select-all, #kundenkarte-select-all-bottom").on("change", function() {
|
||||
var isChecked = $(this).prop("checked");
|
||||
$("input[name=\'selected_products[]\']").prop("checked", isChecked);
|
||||
$("#kundenkarte-select-all, #kundenkarte-select-all-bottom").prop("checked", isChecked);
|
||||
});
|
||||
|
||||
$("input[name=\'selected_products[]\']").on("change", function() {
|
||||
updateSelectAll();
|
||||
});
|
||||
|
||||
updateSelectAll();
|
||||
});
|
||||
</script>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
Loading…
Reference in a new issue