code from amapei
This commit is contained in:
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
*.n
|
||||||
|
*.php
|
||||||
|
config.xml
|
||||||
|
www/plugin/*
|
||||||
|
Makefile
|
||||||
|
tmp/*
|
||||||
|
*node_modules*
|
||||||
|
*.map
|
||||||
|
.haxelib
|
||||||
|
html
|
||||||
|
.temp
|
||||||
|
*.tar
|
||||||
|
|
||||||
|
lang/master/tpl/plugin/*
|
||||||
|
|
||||||
|
lang/fr/tpl/*
|
||||||
|
lang/en/tpl/*
|
||||||
|
lang/de/tpl/*
|
||||||
|
lang/es/tpl/*
|
||||||
|
|
||||||
|
lang/fr/tmp/*.n
|
||||||
|
lang/en/tmp/*.n
|
||||||
|
lang/de/tmp/*.n
|
||||||
|
lang/es/tmp/*.n
|
||||||
|
|
||||||
|
www/lang/*.mo
|
||||||
|
|
||||||
|
www/file/*
|
||||||
|
|
||||||
|
www/js/app.js
|
||||||
|
www/js/libs.js
|
||||||
|
|
||||||
|
|
||||||
|
www/css/.sass-cache
|
||||||
|
www/css/*.css
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) 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
|
||||||
|
this service 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 make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. 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.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
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
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the 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 a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE 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.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
{description}
|
||||||
|
Copyright (C) {year} {fullname}
|
||||||
|
|
||||||
|
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 2 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, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
{signature of Ty Coon}, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
||||||
|
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
ROPTS=-zav --no-p --chmod=u=rw,g=rw,o=r --delete --exclude=www/.htaccess --exclude=.svn --exclude=.git --exclude=*.mtt --exclude=tpl/css --exclude=www/file --exclude=*node_modules* --exclude=*.php --exclude=tests.n
|
||||||
|
LANG=fr
|
||||||
|
PLUGINS?=0
|
||||||
|
ENV?="dev"
|
||||||
|
|
||||||
|
#To compile project with plugins, add PLUGINS=1 or type `export PLUGINS=1`
|
||||||
|
|
||||||
|
install:
|
||||||
|
#copy config file from template
|
||||||
|
cp config.xml.dist config.xml
|
||||||
|
@if [ $(ENV) = "dev" ]; then \
|
||||||
|
make install_dev; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
#setup dev environnement from source
|
||||||
|
install_dev:
|
||||||
|
#set config file to debug=1
|
||||||
|
@sed -e 's?debug=.*?debug="1"?g' --in-place config.xml
|
||||||
|
#install haxe dependencies in .haxelib
|
||||||
|
haxelib newrepo
|
||||||
|
haxelib -always install cagette.hxml
|
||||||
|
haxelib -always install cagetteJs.hxml
|
||||||
|
#template tools
|
||||||
|
haxelib run templo
|
||||||
|
sudo mv temploc2 /usr/bin
|
||||||
|
#extra plugins
|
||||||
|
@if [ $(PLUGINS) = 1 ]; then \
|
||||||
|
haxelib git cagette-pro git@bitbucket.org:bubar/cagette-pro.git; \
|
||||||
|
haxelib git cagette-hosted git@bitbucket.org:bubar/cagette-hosted.git; \
|
||||||
|
haxelib git cagette-wholesale-order git@github.com:bablukid/cagette-wholesale-order.git; \
|
||||||
|
fi
|
||||||
|
#install npm dependencies
|
||||||
|
npm install
|
||||||
|
npm run libs:dev
|
||||||
|
npm run build:js
|
||||||
|
#compile
|
||||||
|
@make css
|
||||||
|
@make compile
|
||||||
|
@make frontend
|
||||||
|
@echo "Well, it looks like everything is fine : librairies are installed, backend and frontend has been compiled !"
|
||||||
|
|
||||||
|
|
||||||
|
#compile backend to Neko
|
||||||
|
compile:
|
||||||
|
@if [ $(PLUGINS) = 1 ]; then \
|
||||||
|
echo "compile Cagette.net with plugins"; \
|
||||||
|
haxe cagetteAllPlugins.hxml; \
|
||||||
|
else \
|
||||||
|
echo "compile Cagette.net core"; \
|
||||||
|
haxe cagette.hxml; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
#compile SASS files to CSS
|
||||||
|
css:
|
||||||
|
npm run build:sass
|
||||||
|
|
||||||
|
#compile frontend to JS
|
||||||
|
frontend:
|
||||||
|
@if [ $(PLUGINS) = 1 ]; then \
|
||||||
|
haxe cagetteAllPluginsJs.hxml; \
|
||||||
|
else \
|
||||||
|
haxe cagetteJs.hxml; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
#update POT file from source
|
||||||
|
i18n:
|
||||||
|
haxe potGeneration.hxml
|
||||||
|
|
||||||
|
#compile templates in each language, required for production env.
|
||||||
|
templates:
|
||||||
|
haxe cagette.hxml -D i18n_generation
|
||||||
|
@make LANG=fr ctemplates
|
||||||
|
@make LANG=en ctemplates
|
||||||
|
@make LANG=de ctemplates
|
||||||
|
|
||||||
|
ctemplates:
|
||||||
|
(cd lang/$(LANG)/tpl; temploc2 -macros macros.mtt -output ../tmp/ *.mtt */*.mtt */*/*.mtt */*/*/*.mtt */*/*/*/*.mtt)
|
||||||
|
|
||||||
|
#Compile and run unit tests
|
||||||
|
tests:
|
||||||
|
@if [ $(PLUGINS) = 1 ]; then \
|
||||||
|
haxe testsPlugins.hxml; \
|
||||||
|
else \
|
||||||
|
haxe tests.hxml; \
|
||||||
|
fi
|
||||||
|
@docker-compose run --workdir="/var/www/www" neko neko tests.n mysql://root:root@mysql/tests
|
||||||
|
|
||||||
|
cp_plugin:
|
||||||
|
cp -R .haxelib/cagette-hosted/git/tpl/* lang/master/tpl/plugin/hosted/
|
||||||
|
cp -R .haxelib/cagette-pro/git/tpl/* lang/master/tpl/plugin/pro/
|
||||||
|
cp -R .haxelib/cagette-wholesale-order/git/tpl/* lang/master/tpl/plugin/who/
|
||||||
|
|
||||||
@@ -1,2 +1,17 @@
|
|||||||
CAGETTE from Artcode
|
# Cagette.net
|
||||||
====================
|
Le logiciel libre des circuits courts.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/bablukid/cagette)
|
||||||
|
|
||||||
|
http://www.cagette.net
|
||||||
|
|
||||||
|
Documentation : http://www.cagette.net/wiki
|
||||||
|
|
||||||
|
English :
|
||||||
|
|
||||||
|
Cagette.net is an open source web app to help people build a better and sustainable food system.
|
||||||
|
Some people call it a "foodhub" : A mix between a groupware and a marketplace, helping consumers to order food from local farmers and producers.
|
||||||
|
|
||||||
|
## Build & install
|
||||||
|
|
||||||
|
Read the Developer page on the wiki to know more about this : http://wiki.cagette.net/dev:index
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName cagette
|
||||||
|
DirectoryIndex index.n
|
||||||
|
DocumentRoot %TRAVIS_BUILD_DIR%/www
|
||||||
|
|
||||||
|
<Directory %TRAVIS_BUILD_DIR%/>
|
||||||
|
Options Indexes FollowSymLinks
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
</VirtualHost>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
-lib dbadmin
|
||||||
|
-lib templo
|
||||||
|
-lib weblog
|
||||||
|
-lib sugoi
|
||||||
|
-lib hxevents
|
||||||
|
-lib cagette-hosted
|
||||||
|
-lib hscript
|
||||||
|
-lib ufront-mail
|
||||||
|
-cp common
|
||||||
|
-cp src
|
||||||
|
-php www/
|
||||||
|
-D spod_macro
|
||||||
|
-main App
|
||||||
|
-D debugip=localhost:18080
|
||||||
|
-D weblogid=cagette
|
||||||
|
--next
|
||||||
|
-dce no
|
||||||
|
-cp common
|
||||||
|
-cp js
|
||||||
|
-lib sugoi
|
||||||
|
-main App
|
||||||
|
-js www/js/app.js
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
-lib templo:3.0.0
|
||||||
|
-lib hxevents:0.4.0
|
||||||
|
-lib thx.semver:0.2.2
|
||||||
|
-lib thx.csv:0.2.0
|
||||||
|
-lib datetime:3.1.1
|
||||||
|
-lib hscript:2.3.0
|
||||||
|
-lib smtpmailer:0.4.0
|
||||||
|
-lib tink_json:0.9.0
|
||||||
|
|
||||||
|
-lib sugoi:git:git@artcode.re:cagette/sugoi.git
|
||||||
|
-lib record-macros:git:git@artcode.re:cagette/record-macros.git
|
||||||
|
-lib dbadmin:git:git@artcode.re:cagette/dbadmin.git
|
||||||
|
|
||||||
|
-cp common
|
||||||
|
-cp src
|
||||||
|
-cp lang/master/tpl
|
||||||
|
-neko www/index.n
|
||||||
|
-main App
|
||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
-lib templo
|
||||||
|
-lib hxevents
|
||||||
|
-lib thx.semver
|
||||||
|
-lib thx.csv
|
||||||
|
-lib datetime
|
||||||
|
-lib hscript
|
||||||
|
-lib smtpmailer
|
||||||
|
-lib tink_json
|
||||||
|
|
||||||
|
-lib sugoi:git:https://github.com/bablukid/sugoi#master
|
||||||
|
-lib record-macros:git:https://github.com/HaxeFoundation/record-macros#master
|
||||||
|
-lib dbadmin:git:https://github.com/ncannasse/dbadmin.git#master
|
||||||
|
|
||||||
|
-lib cagette-pro:git:https://bubar@bitbucket.org/bubar/cagette-pro.git#master
|
||||||
|
-lib cagette-hosted:git:https://bubar@bitbucket.org/bubar/cagette-hosted.git#master
|
||||||
|
-lib cagette-wholesale-order:git:https://github.com/bablukid/cagette-wholesale-order.git#master
|
||||||
|
|
||||||
|
-cp common
|
||||||
|
-cp src
|
||||||
|
-cp lang/master/tpl
|
||||||
|
|
||||||
|
-D plugins
|
||||||
|
|
||||||
|
|
||||||
|
-neko www/index.n
|
||||||
|
-main App
|
||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<project version="2">
|
||||||
|
<!-- Output SWF options -->
|
||||||
|
<output>
|
||||||
|
<movie outputType="Application" />
|
||||||
|
<movie input="" />
|
||||||
|
<movie path="www\index.n" />
|
||||||
|
<movie fps="30" />
|
||||||
|
<movie width="800" />
|
||||||
|
<movie height="600" />
|
||||||
|
<movie version="0" />
|
||||||
|
<movie minorVersion="0" />
|
||||||
|
<movie platform="Neko" />
|
||||||
|
<movie background="#FFFFFF" />
|
||||||
|
</output>
|
||||||
|
<!-- Other classes to be compiled into your SWF -->
|
||||||
|
<classpaths>
|
||||||
|
<class path="common" />
|
||||||
|
<class path="src" />
|
||||||
|
<class path="lang\master\tpl" />
|
||||||
|
</classpaths>
|
||||||
|
<!-- Build options -->
|
||||||
|
<build>
|
||||||
|
<option directives="plugins" />
|
||||||
|
<option flashStrict="False" />
|
||||||
|
<option noInlineOnDebug="False" />
|
||||||
|
<option mainClass="App" />
|
||||||
|
<option enabledebug="False" />
|
||||||
|
<option additional="" />
|
||||||
|
</build>
|
||||||
|
<!-- haxelib libraries -->
|
||||||
|
<haxelib>
|
||||||
|
<library name="templo" />
|
||||||
|
<library name="thx.semver" />
|
||||||
|
<library name="thx.csv" />
|
||||||
|
<library name="hxevents" />
|
||||||
|
<library name="hscript" />
|
||||||
|
<library name="record-macros" />
|
||||||
|
<library name="smtpmailer" />
|
||||||
|
<library name="datetime" />
|
||||||
|
<library name="sugoi:git:https://github.com/bablukid/sugoi#i18n" />
|
||||||
|
<library name="record-macros:git:https://github.com/HaxeFoundation/record-macros#maste" />
|
||||||
|
<library name="dbadmin:git:https://github.com/ncannasse/dbadmin.git#master" />
|
||||||
|
<library name="cagette-pro:git:https://bubar@bitbucket.org/bubar/cagette-pro.git#master" />
|
||||||
|
<library name="cagette-connector:git:https://bubar@bitbucket.org/bubar/cagette-connector.git#master" />
|
||||||
|
<library name="cagette-wholesale-order:git:https://bubar@bitbucket.org/bubar/cagette-wholesale-order.git#master" />
|
||||||
|
<library name="cagette-hosted:git:https://bubar@bitbucket.org/bubar/cagette-hosted.git#master" />
|
||||||
|
</haxelib>
|
||||||
|
<!-- Class files to compile (other referenced classes will automatically be included) -->
|
||||||
|
<compileTargets>
|
||||||
|
<!-- example: <compile path="..." /> -->
|
||||||
|
</compileTargets>
|
||||||
|
<!-- Paths to exclude from the Project Explorer tree -->
|
||||||
|
<hiddenPaths>
|
||||||
|
<hidden path="project.hxml" />
|
||||||
|
<hidden path="lang\fr\tmp" />
|
||||||
|
<hidden path="preLaunch" />
|
||||||
|
<hidden path="art" />
|
||||||
|
<hidden path="www\file" />
|
||||||
|
<hidden path="config.tpl.xml" />
|
||||||
|
<hidden path="clearFileCache.bat" />
|
||||||
|
<hidden path="obj" />
|
||||||
|
</hiddenPaths>
|
||||||
|
<!-- Executed before build -->
|
||||||
|
<preBuildCommand />
|
||||||
|
<!-- Executed after build -->
|
||||||
|
<postBuildCommand alwaysRun="False" />
|
||||||
|
<!-- Other project options -->
|
||||||
|
<options>
|
||||||
|
<option showHiddenPaths="True" />
|
||||||
|
<option testMovie="Custom" />
|
||||||
|
<option testMovieCommand="" />
|
||||||
|
</options>
|
||||||
|
<!-- Plugin storage -->
|
||||||
|
<storage />
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
-dce no
|
||||||
|
-cp common
|
||||||
|
-cp js
|
||||||
|
-lib sugoi:git:https://github.com/bablukid/sugoi#i18n
|
||||||
|
-lib hxleaflet:git:https://github.com/bablukid/hxleaflet
|
||||||
|
-lib cagette-hosted
|
||||||
|
-lib react
|
||||||
|
-lib react-router-4
|
||||||
|
-lib modular
|
||||||
|
-lib tink_json
|
||||||
|
|
||||||
|
-D plugins
|
||||||
|
-D no-deprecation-warnings
|
||||||
|
|
||||||
|
-main App
|
||||||
|
-js www/js/app.js
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
-dce no
|
||||||
|
-cp common
|
||||||
|
-cp js
|
||||||
|
-lib sugoi:git:git@artcode.re:cagette/sugoi.git
|
||||||
|
-lib hxleaflet:git:git@artcode.re:cagette/hxleaflet.git
|
||||||
|
-lib react:1.4.0
|
||||||
|
-lib react-router-4:0.2.3
|
||||||
|
-lib modular:0.10.15
|
||||||
|
-lib tink_json:0.9.0
|
||||||
|
-main App
|
||||||
|
-debug
|
||||||
|
-js www/js/app.js
|
||||||
|
-D no-deprecation-warnings
|
||||||
Executable
+472
@@ -0,0 +1,472 @@
|
|||||||
|
/**
|
||||||
|
* Common.hx : Shared entities between server and client
|
||||||
|
*/
|
||||||
|
|
||||||
|
@:keep
|
||||||
|
typedef OrderSimple = {
|
||||||
|
products: Array<{
|
||||||
|
product:ProductInfo,
|
||||||
|
quantity:Int
|
||||||
|
}>,
|
||||||
|
total:Float
|
||||||
|
}
|
||||||
|
|
||||||
|
//A temporary order, waiting for being paid and definitely recorded.
|
||||||
|
@:keep
|
||||||
|
typedef OrderInSession = {
|
||||||
|
products:Array <{
|
||||||
|
productId:Int,
|
||||||
|
quantity:Float,
|
||||||
|
#if !js
|
||||||
|
?product:db.Product,
|
||||||
|
?distributionId:Int,
|
||||||
|
#end
|
||||||
|
} > ,
|
||||||
|
?userId:Int,
|
||||||
|
total:Float, //price to pay
|
||||||
|
?paymentOp:Int, //payment operation ID
|
||||||
|
}
|
||||||
|
|
||||||
|
@:keep
|
||||||
|
typedef ProductInfo = {
|
||||||
|
id : Int,
|
||||||
|
name : String,
|
||||||
|
ref : Null<String>,
|
||||||
|
image : Null<String>,
|
||||||
|
contractId : Int,
|
||||||
|
price : Float,
|
||||||
|
vat : Null<Float>, //VAT rate
|
||||||
|
vatValue : Null<Float>, //amount of VAT included in the price
|
||||||
|
contractTax : Null<Float>, //pourcentage de commission défini dans le contrat
|
||||||
|
contractTaxName : Null<String>, //label pour la commission : ex: "frais divers"
|
||||||
|
desc : Null<String>,
|
||||||
|
categories : Null<Array<Int>>, //used in old shop
|
||||||
|
subcategories : Null<Array<Int>>, //used in new shop
|
||||||
|
orderable : Bool, //can be currently ordered
|
||||||
|
stock: Null<Float>, //available stock
|
||||||
|
hasFloatQt : Bool,
|
||||||
|
qt:Null<Float>,
|
||||||
|
unitType:Null<Unit>,
|
||||||
|
organic:Bool,
|
||||||
|
variablePrice:Bool,
|
||||||
|
wholesale:Bool,
|
||||||
|
active:Bool,
|
||||||
|
?distributionId:Null<Int>, //in the context of a distrib
|
||||||
|
}
|
||||||
|
|
||||||
|
@:keep
|
||||||
|
typedef ProductWithQuantity = {
|
||||||
|
product: ProductInfo,
|
||||||
|
quantity: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Unit{
|
||||||
|
Piece;
|
||||||
|
Kilogram;
|
||||||
|
Gram;
|
||||||
|
Litre;
|
||||||
|
Centilitre;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef CategoryInfo = {
|
||||||
|
id:Int,
|
||||||
|
name:String,
|
||||||
|
?subcategories:Array<CategoryInfo>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* datas used with the "tagger" ajax class
|
||||||
|
*/
|
||||||
|
@:keep
|
||||||
|
typedef TaggerInfos = {
|
||||||
|
products:Array<{product:ProductInfo,categories:Array<Int>}>,//tagged products
|
||||||
|
categories : Array<{id:Int,categoryGroupName:String,color:String,tags:Array<{id:Int,name:String}>}>, //groupe de categories + tags
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links in navbars for plugins
|
||||||
|
*/
|
||||||
|
typedef Link = {
|
||||||
|
id:String,
|
||||||
|
link:String,
|
||||||
|
name:String,
|
||||||
|
?icon:String,
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Block = {
|
||||||
|
id:String,
|
||||||
|
title:String,
|
||||||
|
?icon:String,
|
||||||
|
html:String
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef UserOrder = {
|
||||||
|
id:Int,
|
||||||
|
userId:Int,
|
||||||
|
userName:String,
|
||||||
|
?userEmail : String,
|
||||||
|
|
||||||
|
?userId2:Int,
|
||||||
|
?userName2:String,
|
||||||
|
?userEmail2:String,
|
||||||
|
|
||||||
|
//deprecated
|
||||||
|
?productId:Int,
|
||||||
|
?productRef:String,
|
||||||
|
?productName:String,
|
||||||
|
?productPrice:Float,
|
||||||
|
?productImage:String,
|
||||||
|
?productQt:Float,
|
||||||
|
?productUnit:Unit,
|
||||||
|
|
||||||
|
?productHasFloatQt:Bool,
|
||||||
|
?productHasVariablePrice:Bool,
|
||||||
|
|
||||||
|
//new way
|
||||||
|
?product:ProductInfo,
|
||||||
|
|
||||||
|
quantity:Float,
|
||||||
|
smartQt:String,
|
||||||
|
subTotal:Float,
|
||||||
|
|
||||||
|
fees:Null<Float>,
|
||||||
|
percentageName:Null<String>,
|
||||||
|
percentageValue:Null<Float>,
|
||||||
|
total:Float,
|
||||||
|
|
||||||
|
//flags
|
||||||
|
paid:Bool,
|
||||||
|
invertSharedOrder:Bool,
|
||||||
|
canceled:Bool,
|
||||||
|
canModify:Bool,
|
||||||
|
|
||||||
|
contractId:Int,
|
||||||
|
contractName:String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef PlaceInfos = {
|
||||||
|
id:Int,
|
||||||
|
name:String,
|
||||||
|
address1:String,
|
||||||
|
address2:String,
|
||||||
|
zipCode:String,
|
||||||
|
city:String,
|
||||||
|
latitude:Float,
|
||||||
|
longitude:Float
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef UserInfo = {
|
||||||
|
id:Int,
|
||||||
|
name:String,
|
||||||
|
email:String,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef GroupOnMap = {
|
||||||
|
id:Int,
|
||||||
|
name:String,
|
||||||
|
image:String,
|
||||||
|
place:PlaceInfos,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderFlags {
|
||||||
|
InvertSharedOrder; //invert order when there is a shared/alternated order
|
||||||
|
//Canceled; //flag for cancelled orders, qt should always be 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef OrderByProduct = {
|
||||||
|
quantity:Float,
|
||||||
|
smartQt:String,
|
||||||
|
pid:Int,
|
||||||
|
pname:String,
|
||||||
|
ref:String,
|
||||||
|
priceHT:Float,
|
||||||
|
priceTTC:Float,
|
||||||
|
vat:Float,
|
||||||
|
totalHT:Float,
|
||||||
|
totalTTC:Float,
|
||||||
|
weightOrVolume:String,
|
||||||
|
};
|
||||||
|
typedef OrderByEndDate = {date: String,contracts: Array<String>};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Event enum used for plugins.
|
||||||
|
|
||||||
|
As in most CMS event systems,
|
||||||
|
the events (or "triggers") can be caught by plugins
|
||||||
|
to perform an action or modifiy data carried by the event.
|
||||||
|
|
||||||
|
**/
|
||||||
|
enum Event {
|
||||||
|
|
||||||
|
Page(uri:String); //a page is displayed
|
||||||
|
Nav(nav:Array<Link>, name:String, ?id:Int); //a navigation is displayed, optionnal object id if needed
|
||||||
|
Blocks(blocks:Array<Block>, name:String, ?context:Dynamic); //HTML blocks that can be displayed on a page
|
||||||
|
|
||||||
|
#if sys
|
||||||
|
SendEmail(message : sugoi.mail.Mail); //an email is sent
|
||||||
|
NewMember(user:db.User,group:db.Amap); //a new member is added to a group
|
||||||
|
NewGroup(group:db.Amap, author:db.User); //a new group is created
|
||||||
|
|
||||||
|
//Distributions
|
||||||
|
PreNewDistrib(contract:db.Contract); //when displaying the insert distribution form
|
||||||
|
NewDistrib(distrib:db.Distribution); //when a new distrinbution is created
|
||||||
|
PreEditDistrib(distrib:db.Distribution);
|
||||||
|
EditDistrib(distrib:db.Distribution);
|
||||||
|
DeleteDistrib(distrib:db.Distribution);
|
||||||
|
PreNewDistribCycle(cycle:db.DistributionCycle);
|
||||||
|
NewDistribCycle(cycle:db.DistributionCycle);
|
||||||
|
MultiDistribEvent(md:MultiDistrib);
|
||||||
|
|
||||||
|
//Products
|
||||||
|
PreNewProduct(contract:db.Contract); //when displaying the insert distribution form
|
||||||
|
NewProduct(product:db.Product); //when a new product is created
|
||||||
|
PreEditProduct(product:db.Product);
|
||||||
|
EditProduct(product:db.Product);
|
||||||
|
DeleteProduct(product:db.Product);
|
||||||
|
BatchEnableProducts(data:{pids:Array<Int>,enable:Bool});
|
||||||
|
ProductInfosEvent(p:ProductInfo,?d:db.Distribution); //when infos about a product are displayed
|
||||||
|
|
||||||
|
//Contracts
|
||||||
|
EditContract(contract:db.Contract,form:sugoi.form.Form);
|
||||||
|
DuplicateContract(contract:db.Contract);
|
||||||
|
DeleteContract(contract:db.Contract);
|
||||||
|
|
||||||
|
//crons
|
||||||
|
DailyCron;
|
||||||
|
HourlyCron;
|
||||||
|
MinutelyCron;
|
||||||
|
|
||||||
|
//orders
|
||||||
|
MakeOrder(orders:Array<db.UserContract>);
|
||||||
|
StockMove(order:{product:db.Product, move:Float}); //when a stock is modified
|
||||||
|
|
||||||
|
//payments
|
||||||
|
GetPaymentTypes(data:{types:Array<payment.Payment>});
|
||||||
|
NewOperation(op:db.Operation);
|
||||||
|
|
||||||
|
#end
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Product Taxonomy structure
|
||||||
|
*/
|
||||||
|
typedef TxpDictionnary = {
|
||||||
|
products:Map<Int,{id:Int,name:String,category:Int,subCategory:Int}>,
|
||||||
|
categories:Map<Int,{id:Int,name:String}>,
|
||||||
|
subCategories:Map<Int,{id:Int,name:String}>,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tutorials
|
||||||
|
*/
|
||||||
|
enum TutoAction {
|
||||||
|
TAPage(uri:String);
|
||||||
|
TANext;
|
||||||
|
|
||||||
|
}
|
||||||
|
enum TutoPlacement {
|
||||||
|
TPTop;
|
||||||
|
TPBottom;
|
||||||
|
TPLeft;
|
||||||
|
TPRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TutoDatas {
|
||||||
|
|
||||||
|
public static var TUTOS;
|
||||||
|
|
||||||
|
#if js
|
||||||
|
//async
|
||||||
|
public static function get(tuto:String, callback:Dynamic->Void){
|
||||||
|
#if !test
|
||||||
|
sugoi.i18n.Locale.init(App.instance.LANG, function(t:sugoi.i18n.GetText){
|
||||||
|
App.instance.t = t;
|
||||||
|
init(t);
|
||||||
|
var tuto = TUTOS.get(tuto);
|
||||||
|
callback(tuto);
|
||||||
|
});
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
//sync
|
||||||
|
public static function get(tuto:String):{name:String, steps:Array<{element:String,text:String,action:TutoAction,placement:TutoPlacement}>}
|
||||||
|
{
|
||||||
|
sugoi.i18n.Locale.init(App.current.getLang());
|
||||||
|
init(sugoi.i18n.Locale.texts);
|
||||||
|
return TUTOS.get(tuto);
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
static function init(t:sugoi.i18n.GetText){
|
||||||
|
|
||||||
|
TUTOS = [
|
||||||
|
"intro" => {
|
||||||
|
name:t._("Guided tour for the coordinator"),
|
||||||
|
steps:[
|
||||||
|
{
|
||||||
|
element:null,
|
||||||
|
text:t._("<p>In order to better discover Cagette.net, we propose to do a guided tour of the user interface of the software. <br/> You will then have a global overview on the different tools that are available to you.</p><p>You will be able to stop and start again this tutorial whenever you want.</p>"),
|
||||||
|
action: TANext,
|
||||||
|
placement : null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element:"ul.nav.navbar-left",
|
||||||
|
text:t._( "This part of the navigation bar is visible by all members.</br>It allows to access to the three main pages: <ul><li> The <b>home page</b> which displays orders and the delivery planning.</li><li> On the <b>My account</b> page, you can update your personal information and check your orders history</li><li> On the <b>Farmers</b> page, you can see all farmers and coordinators of the group</ul>"),
|
||||||
|
action: TANext,
|
||||||
|
placement : TPBottom
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element:"ul.nav.navbar-right",
|
||||||
|
text:t._("This part is <b>for coordinators only.</b>Here you will be able to manage the register of members, orders, products, etc.<br/><p>Now click on the <b>Members</b> section</p>"),
|
||||||
|
action: TAPage("/member"),
|
||||||
|
placement : TPBottom
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:".article .table td:first",
|
||||||
|
text:t._("The purpose of this section is to administrate the list of your members.<br/>Every time that you register a new membrer, an account will be created for him/her. Now the member can join you at Cagette.net and order or consult the planning of the deliveries.<p>Now click on a <b>member</b> in the list</p>"),
|
||||||
|
action: TAPage("/member/view/*"),
|
||||||
|
placement : TPRight
|
||||||
|
},{
|
||||||
|
element:".article:first",
|
||||||
|
text:t._("This is the page of a member. Here you can : <ul><li>see and change their contact details</li><li>manage the membership fee of your group</li><li>see a summary of their orders</li></ul>"),
|
||||||
|
action: TANext,
|
||||||
|
placement : TPRight
|
||||||
|
},{
|
||||||
|
element:"ul.nav #contractadmin",
|
||||||
|
text:t._("Now let's have a look at the <b>contracts</b> section which is very important for coordinators."),
|
||||||
|
action: TAPage("/contractAdmin"),
|
||||||
|
placement : TPBottom
|
||||||
|
},{
|
||||||
|
element:"#contracts",
|
||||||
|
text:t._("Here you find the list of <b>contracts</b>.They inculde a start date, a end date, and represent your relationship with a farmer. <br/><p>Here you can manage :<ul><li>the list of products of this farmer</li><li>the orders of members for this farmer</li><li>and plan the delivery schedule</li></ul></p>"),
|
||||||
|
action: TANext,
|
||||||
|
placement : TPBottom
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:"#places",
|
||||||
|
text:t._("Here you can manage the list of <b>delivery places</b>.<br/>Don't forget to key-in the complete address as a map will be displayed based on this address"),
|
||||||
|
action: TANext,
|
||||||
|
placement : TPTop
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:"#contracts table .btn:first",
|
||||||
|
text:t._("Let's look closer at how to manage a contract. <b>Click on this button</b>"),
|
||||||
|
action: TAPage("/contractAdmin/view/*"),
|
||||||
|
placement : TPBottom
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:".table.table-bordered:first",
|
||||||
|
text:t._("Here is a summary of the contract.<br/>There are two types of contracts:<ul><li>Constant contracts: the member commits on buying the same products during the whole duration of the contract</li><li>Variable contracts: the member can choose what he buys for each delivery.</li></ul>"),
|
||||||
|
action: TANext,
|
||||||
|
placement : TPRight
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:"#subnav #products",
|
||||||
|
text:t._("Let's see now the page <b>Products</b>"),
|
||||||
|
action : TAPage("/contractAdmin/products/*"),
|
||||||
|
placement:TPRight
|
||||||
|
},{
|
||||||
|
element:".article .table",
|
||||||
|
text:t._("On this page, you can manage the list of products offered by this supplier.<br/>Define at least the name and the price of products. It is also possible to add a description and a picture."),
|
||||||
|
action: TANext,
|
||||||
|
placement : TPTop
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:"#subnav #deliveries",
|
||||||
|
text:t._("Let's see the <b>deliveries</b> page"),
|
||||||
|
action : TAPage("/contractAdmin/distributions/*"),
|
||||||
|
placement:TPRight
|
||||||
|
},{
|
||||||
|
element:".article .table",
|
||||||
|
text:t._("Here we can manage the list of deliveries for this supplier.<br/>In the software, a delivery has a date, a start time, and an end time. The location of the delivery must also be defined, by using the list that we have already seen."),
|
||||||
|
action: TANext,
|
||||||
|
placement : TPLeft
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:"#subnav #orders",
|
||||||
|
text:t._("Let's see now the <b>Orders</b> page"),
|
||||||
|
action : TAPage("/contractAdmin/orders/*"), //can fail if the contract is variable because the uri is different
|
||||||
|
placement:TPRight
|
||||||
|
},{
|
||||||
|
element:".article .table",
|
||||||
|
text:t._("Here we can manage the list of orders for this supplier.<br/>If you choose to \"open orders\" to members, they will be able to make their orders online themselves.<br/>This page will centralize automatically the orders for this supplier. Otherwise, as a coordinator, you will be able to enter orders on behalf of a member."),
|
||||||
|
action: TANext,
|
||||||
|
placement : TPLeft
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:"ul.nav #messages",
|
||||||
|
text:t._("<p>We have seen the main features related to contracts.</p><p>Let's see the <b>messaging</b> section.</p>"),
|
||||||
|
action: TAPage("/messages"),
|
||||||
|
placement : TPBottom
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:null,
|
||||||
|
text:t._("<p>The messaging section allows you to send e-mails to different lists of members. It is not necessary anymore to maintain a lot of lists of e-mails depending on contracts, as all these lists are automatically generated.</p> <p>E-mails are sent with your e-mail address as sender, so you will receive answers in your own mailbox.</p>"),
|
||||||
|
action: TANext,
|
||||||
|
placement : null
|
||||||
|
|
||||||
|
},{
|
||||||
|
element:"ul.nav #amapadmin",
|
||||||
|
text:t._("Click here now on this page"),
|
||||||
|
action : TAPage("/amapadmin"),
|
||||||
|
placement : TPBottom,
|
||||||
|
},{
|
||||||
|
element:"#subnav",
|
||||||
|
text:t._("<p>In this last page, you can configure everything that is related to your group.</p><p>The page <b>Access rights</b> is important as it is where you can define other coordinators among members. They will then be able to manage one or many contracts, send emails, etc.</p>"),
|
||||||
|
action:TANext,
|
||||||
|
placement : TPBottom
|
||||||
|
},{
|
||||||
|
element:"#footer",
|
||||||
|
text:t._("<p>This is the last step of this tutorial. I hope that it gave you a good overview of this software.<br/>To go further, do not hesitate to look at the <b>documentation</b>. The link is always available at the bottom of the screen.</p>"),
|
||||||
|
action:TANext,
|
||||||
|
placement : TPBottom
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order Reports
|
||||||
|
*/
|
||||||
|
enum OrdersReportGroupOption{
|
||||||
|
ByMember;
|
||||||
|
ByProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrdersReportFormatOption{
|
||||||
|
Table;
|
||||||
|
Csv;
|
||||||
|
PrintableList; //list de distrib ?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Report Options : should be usable in an URL, an API call...
|
||||||
|
typedef OrdersReportOptions = {
|
||||||
|
//time scope
|
||||||
|
startDate:Date,
|
||||||
|
endDate:Date,
|
||||||
|
|
||||||
|
//formatting
|
||||||
|
?groupBy:OrdersReportGroupOption, //group order by...
|
||||||
|
?format:OrdersReportFormatOption, //table , csv ?
|
||||||
|
|
||||||
|
//filters :
|
||||||
|
?groups:Array<Int>,
|
||||||
|
?contracts:Array<Int>, //which contracts
|
||||||
|
?distributions:Array<Int>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatting tools used on both client and server side.
|
||||||
|
*
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class Formatting
|
||||||
|
{
|
||||||
|
|
||||||
|
/** smart quantity filter : display easier-to-read quantity when it's floats
|
||||||
|
*
|
||||||
|
* 0.33 x Lemon 12kg => 2kg Lemon
|
||||||
|
*/
|
||||||
|
public static function smartQt(orderQt:Float,productQt:Float,unit:Unit):String{
|
||||||
|
return formatNum(orderQt * productQt) + " " + Formatting.unit(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function formatNum(n:Float):String {
|
||||||
|
if (n == null) return "";
|
||||||
|
|
||||||
|
//arrondi a 2 apres virgule
|
||||||
|
var out = Std.string(roundTo(n, 2));
|
||||||
|
|
||||||
|
//ajout un zéro, 1,8-->1,80
|
||||||
|
if (out.indexOf(".")!=-1 && out.split(".")[1].length == 1) out = out +"0";
|
||||||
|
|
||||||
|
//virgule et pas point
|
||||||
|
return out.split(".").join(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Round a number to r digits after coma.
|
||||||
|
*/
|
||||||
|
public static function roundTo(n:Float, r:Int):Float {
|
||||||
|
return Math.round(n * Math.pow(10,r)) / Math.pow(10,r) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function parseFloat(s:String):Float{
|
||||||
|
if(s.indexOf(",")>0){
|
||||||
|
return Std.parseFloat(StringTools.replace(s,",","."));
|
||||||
|
}else{
|
||||||
|
return Std.parseFloat(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a unit
|
||||||
|
* @param u -
|
||||||
|
*/
|
||||||
|
public static function unit(u:Unit):String{
|
||||||
|
/*t = sugoi.i18n.Locale.texts;
|
||||||
|
if(u==null) return t._("piece||unit of a product)");
|
||||||
|
return switch(u){
|
||||||
|
case Kilogram: t._("Kg.||kilogramms");
|
||||||
|
case Gram: t._("g.||gramms");
|
||||||
|
case Piece: t._("piece||unit of a product)");
|
||||||
|
case Litre: t._("L.||liter");
|
||||||
|
case Centilitre: t._("cl.||centiliter");
|
||||||
|
}*/
|
||||||
|
if(u==null) return "pièce(s)";
|
||||||
|
return switch(u){
|
||||||
|
case Kilogram: "Kg.";
|
||||||
|
case Gram: "g.";
|
||||||
|
case Piece: "pièce(s)";
|
||||||
|
case Litre: "L.";
|
||||||
|
case Centilitre:"cl.";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
|||||||
|
//React lib
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import react.ReactDOM;
|
||||||
|
import react.*;
|
||||||
|
import react.router.*;
|
||||||
|
//custom components
|
||||||
|
import react.order.*;
|
||||||
|
import react.product.*;
|
||||||
|
import react.store.*;
|
||||||
|
import react.map.*;
|
||||||
|
import react.user.*;
|
||||||
|
|
||||||
|
//require bootstrap JS since it's bundled with browserify
|
||||||
|
//@:jsRequire('bootstrap') extern class Bootstrap{}
|
||||||
|
//@:jsRequire('jquery') extern class JQ extends js.jquery.JQuery{}
|
||||||
|
|
||||||
|
class App {
|
||||||
|
|
||||||
|
public static var instance : App;
|
||||||
|
public var LANG : String;
|
||||||
|
public var currency : String; //currency symbol like € or $
|
||||||
|
public var t : sugoi.i18n.GetText;//gettext translator
|
||||||
|
|
||||||
|
//i dont want to use redux now... saved state from react.OrderBox
|
||||||
|
public static var SAVED_ORDER_STATE : Dynamic;
|
||||||
|
|
||||||
|
function new(?lang="fr",?currency="€") {
|
||||||
|
//singleton
|
||||||
|
instance = this;
|
||||||
|
if(lang!=null) this.LANG = lang;
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a jquery object like $() in javascript
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public static inline function j(r:Dynamic):js.JQuery {
|
||||||
|
return new js.JQuery(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function jq(r:Dynamic):js.jquery.JQuery{
|
||||||
|
return new js.jquery.JQuery(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The JS App will be available as "_" in the document.
|
||||||
|
*/
|
||||||
|
public static function main() {
|
||||||
|
|
||||||
|
//untyped js.Browser.window.$ = js.Lib.require("jQuery");
|
||||||
|
untyped js.Browser.window._ = new App();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCart() {
|
||||||
|
return new ShopCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTagger(cid:Int ) {
|
||||||
|
return new Tagger(cid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTuto(name:String, step:Int) {
|
||||||
|
new Tuto(name,step);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove method for IE compat
|
||||||
|
*/
|
||||||
|
public function remove(el:js.html.Element){
|
||||||
|
if (el == null) return;
|
||||||
|
el.parentElement.removeChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVATBox(ttcprice:Float,currency:String,rates:String,vat:Float,formName:String){
|
||||||
|
|
||||||
|
var input = js.Browser.document.querySelector('form input[name="${formName}_price"]');
|
||||||
|
|
||||||
|
remove( js.Browser.document.querySelector('form input[name="${formName}_vat"]').parentElement.parentElement );
|
||||||
|
|
||||||
|
ReactDOM.render(jsx('<$VATBox ttc="$ttcprice" currency="$currency" vatRates="$rates" vat="$vat" formName="$formName"/>'), input.parentElement);
|
||||||
|
|
||||||
|
//remove(input);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public function getProductComposer(){
|
||||||
|
//js.Browser.document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
|
//ReactDOM.render(jsx('<$ComposerApp/>'), js.Browser.document.getElementById("app"));
|
||||||
|
//});
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the form element and replace it by a react js component
|
||||||
|
* @param divId
|
||||||
|
* @param productName
|
||||||
|
* @param txpProductId
|
||||||
|
* @param formName
|
||||||
|
*/
|
||||||
|
public function getProductInput(divId:String, productName:String, txpProductId:String, formName:String ){
|
||||||
|
|
||||||
|
js.Browser.document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
|
|
||||||
|
//dirty stuff to remove "real" input, and replace it by the react one
|
||||||
|
App.j("form input[name='"+formName+"_name']").parent().parent().remove();
|
||||||
|
App.j("form select[name='" + formName+"_txpProductId']").parent().parent().remove();
|
||||||
|
|
||||||
|
if (txpProductId == null) txpProductId = "";
|
||||||
|
|
||||||
|
ReactDOM.render(jsx('<$ProductInput productName="$productName" txpProductId="$txpProductId" formName="$formName"/>'), js.Browser.document.getElementById(divId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initReportHeader(){
|
||||||
|
ReactDOM.render(jsx('<$ReportHeader />'), js.Browser.document.querySelector('div.reportHeaderContainer'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initOrderBox(userId:Int, distributionId:Int, contractId:Int, contractType:Int, date:String, place:String, userName:String, currency:String, hasPayments:Bool,callbackUrl:String){
|
||||||
|
|
||||||
|
untyped App.j("#myModal").modal();
|
||||||
|
var onValidate = function() js.Browser.location.href = callbackUrl;
|
||||||
|
var node = js.Browser.document.querySelector('#myModal .modal-body');
|
||||||
|
ReactDOM.unmountComponentAtNode(node); //the previous modal DOM element is still there, so we need to destroy it
|
||||||
|
ReactDOM.render(jsx('<$OrderBox userId="$userId" distributionId="$distributionId"
|
||||||
|
contractId="$contractId" contractType="$contractType" date="$date" place="$place" userName="$userName"
|
||||||
|
onValidate=$onValidate currency=$currency hasPayments=$hasPayments />'),node,postReact);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function postReact(){
|
||||||
|
trace("post react");
|
||||||
|
haxe.Timer.delay(function(){
|
||||||
|
untyped jq('[data-toggle="tooltip"]').tooltip();
|
||||||
|
untyped jq('[data-toggle="popover"]').popover();
|
||||||
|
},500);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function roundTo(n:Float, r:Int):Float {
|
||||||
|
return Math.round(n * Math.pow(10,r)) / Math.pow(10,r) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajax loads a page and display it in a modal window
|
||||||
|
* @param url
|
||||||
|
* @param title
|
||||||
|
*/
|
||||||
|
public function overlay(url:String,?title,?large=true) {
|
||||||
|
|
||||||
|
if (title != null) title = StringTools.urlDecode(title);
|
||||||
|
|
||||||
|
var r = new haxe.Http(url);
|
||||||
|
r.onData = function(data) {
|
||||||
|
|
||||||
|
//setup body and title
|
||||||
|
var m = App.j("#myModal");
|
||||||
|
m.find(".modal-body").html(data);
|
||||||
|
if (title != null) m.find(".modal-title").html(title);
|
||||||
|
|
||||||
|
if (!large) m.find(".modal-dialog").removeClass("modal-lg");
|
||||||
|
|
||||||
|
|
||||||
|
untyped App.j('#myModal').modal(); //bootstrap 3 modal window
|
||||||
|
|
||||||
|
}
|
||||||
|
r.request();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a login box
|
||||||
|
*/
|
||||||
|
public function loginBox(redirectUrl:String,?message:String,?phoneRequired=false) {
|
||||||
|
var m = App.j("#myModal");
|
||||||
|
m.find(".modal-title").html("S'identifier");
|
||||||
|
m.find(".modal-dialog").removeClass("modal-lg");
|
||||||
|
untyped m.modal();
|
||||||
|
ReactDOM.render(jsx('<$LoginBox redirectUrl="$redirectUrl" message=$message phoneRequired="$phoneRequired"/>'), js.Browser.document.querySelector('#myModal .modal-body'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a sign up box
|
||||||
|
*/
|
||||||
|
public function registerBox(redirectUrl:String,?message:String,?phoneRequired=false) {
|
||||||
|
var m = App.j("#myModal");
|
||||||
|
m.find(".modal-title").html("S'inscrire");
|
||||||
|
m.find(".modal-dialog").removeClass("modal-lg");
|
||||||
|
untyped m.modal();
|
||||||
|
ReactDOM.render(jsx('<$RegisterBox redirectUrl="$redirectUrl" message=$message phoneRequired="$phoneRequired"/>'), js.Browser.document.querySelector('#myModal .modal-body'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shop(place:Int, date:String) {
|
||||||
|
ReactDOM.render(jsx('<$Store date=$date place=$place/>'), js.Browser.document.querySelector('#shop'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupMap(lat:String,lng:String,address:String) {
|
||||||
|
ReactDOM.render(jsx('<$GroupMapRoot lat="$lat" lng="$lng" address="$address"/>'), js.Browser.document.querySelector('#map'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get values of a bunch of checked checkboxes
|
||||||
|
* @param formSelector
|
||||||
|
*/
|
||||||
|
public function getCheckboxesId(formSelector:String):Array<String>{
|
||||||
|
var out = [];
|
||||||
|
var checkboxes = js.Browser.document.querySelectorAll(formSelector + " input[type=checkbox]");
|
||||||
|
for ( input in checkboxes ){
|
||||||
|
var input : js.html.InputElement = cast input;
|
||||||
|
if ( input.checked ) out.push(input.value);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if plugins
|
||||||
|
public function getHostedPlugin(){
|
||||||
|
return new hosted.js.App();
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set up a warning message when leaving the page
|
||||||
|
*/
|
||||||
|
public function setWarningOnUnload(active:Bool, ?msg:String){
|
||||||
|
if (active){
|
||||||
|
js.Browser.window.addEventListener("beforeunload", warn);
|
||||||
|
}else{
|
||||||
|
js.Browser.window.removeEventListener("beforeunload", warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function warn(e:js.html.Event) {
|
||||||
|
var msg = "Voulez vous vraiment quitter cette page ?";
|
||||||
|
//js.Browser.window.confirm(msg);
|
||||||
|
untyped e.returnValue = msg; //Gecko + IE
|
||||||
|
e.preventDefault();
|
||||||
|
return msg; //Gecko + Webkit, Safari, Chrome etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anti Doubleclick with btn elements.
|
||||||
|
* Can be bypassed by adding a .btn-noAntiDoubleClick class
|
||||||
|
*/
|
||||||
|
public function antiDoubleClick(){
|
||||||
|
|
||||||
|
for( n in js.Browser.document.querySelectorAll(".btn:not(.btn-noAntiDoubleClick)") ){
|
||||||
|
n.addEventListener("click",function(e:js.html.MouseEvent){
|
||||||
|
var x = untyped e.target;
|
||||||
|
x.classList.add("disabled");
|
||||||
|
haxe.Timer.delay(function(){
|
||||||
|
x.classList.remove("disabled");
|
||||||
|
},1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+445
@@ -0,0 +1,445 @@
|
|||||||
|
import Common;
|
||||||
|
import js.JQuery;
|
||||||
|
/**
|
||||||
|
* JS Shopping Cart
|
||||||
|
*
|
||||||
|
* @author fbarbut<francois.barbut@gmail.com>
|
||||||
|
*/
|
||||||
|
class ShopCart
|
||||||
|
{
|
||||||
|
|
||||||
|
public var products : Map<Int,ProductInfo>; //product db
|
||||||
|
public var productsArray : Array<ProductInfo>; //to keep order of products
|
||||||
|
public var categories : Array<{name:String,pinned:Bool,categs:Array<CategoryInfo>}>; //categ db
|
||||||
|
public var pinnedCategories : Array<{name:String,pinned:Bool,categs:Array<CategoryInfo>}>; //categ db
|
||||||
|
public var order : OrderInSession;
|
||||||
|
|
||||||
|
var loader : JQuery; //ajax loader gif
|
||||||
|
|
||||||
|
//for scroll mgmt
|
||||||
|
var cartTop : Int;
|
||||||
|
var cartLeft : Int;
|
||||||
|
var cartWidth : Int;
|
||||||
|
var jWindow : JQuery;
|
||||||
|
var cartContainer : JQuery;
|
||||||
|
|
||||||
|
var date : String;
|
||||||
|
var place : Int;
|
||||||
|
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
products = new Map();
|
||||||
|
productsArray = [];
|
||||||
|
|
||||||
|
order = cast { products:[] };
|
||||||
|
categories = [];
|
||||||
|
pinnedCategories = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function add(pid:Int) {
|
||||||
|
loader.show();
|
||||||
|
|
||||||
|
var q = App.j('#productQt' + pid).val();
|
||||||
|
var qt = 0.0;
|
||||||
|
var p = this.products.get(pid);
|
||||||
|
if (p.hasFloatQt) {
|
||||||
|
q = StringTools.replace(q, ",", ".");
|
||||||
|
qt = Std.parseFloat(q);
|
||||||
|
}else {
|
||||||
|
qt = Std.parseInt(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qt == null) {
|
||||||
|
qt = 1;
|
||||||
|
}
|
||||||
|
//trace("qté : "+qt);
|
||||||
|
|
||||||
|
//add server side
|
||||||
|
var r = new haxe.Http('/shop/add/$pid/$qt');
|
||||||
|
|
||||||
|
r.onData = function(data:String) {
|
||||||
|
|
||||||
|
loader.hide();
|
||||||
|
|
||||||
|
var d = haxe.Json.parse(data);
|
||||||
|
if (!d.success) js.Browser.alert("Erreur : "+d);
|
||||||
|
|
||||||
|
//add locally
|
||||||
|
subAdd(pid, qt);
|
||||||
|
render();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
r.request();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function subAdd(pid, qt:Float ) {
|
||||||
|
|
||||||
|
for ( p in order.products) {
|
||||||
|
if (p.productId == pid) {
|
||||||
|
|
||||||
|
p.quantity += qt;
|
||||||
|
render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
order.products.push( { productId:pid, quantity:qt } );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the shopping cart and total
|
||||||
|
*/
|
||||||
|
function render() {
|
||||||
|
var c = App.j("#cart");
|
||||||
|
c.empty();
|
||||||
|
|
||||||
|
//render items in shopping cart
|
||||||
|
c.append( Lambda.map(order.products, function( x ) {
|
||||||
|
var p = this.products.get(x.productId);
|
||||||
|
if (p == null) {
|
||||||
|
//the product may have been disabled by an admin
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var btn = "<a onClick='cart.remove(" + p.id + ")' class='btn btn-default btn-xs' data-toggle='tooltip' data-placement='top' title='Retirer de la commande'><span class='glyphicon glyphicon-remove'></span></a> ";
|
||||||
|
return "<div class='row'>
|
||||||
|
<div class = 'order col-md-9' > <b> " + x.quantity + " </b> x " + p.name+" </div>
|
||||||
|
<div class = 'col-md-3'> "+btn+"</div>
|
||||||
|
</div>";
|
||||||
|
}).join("\n") );
|
||||||
|
|
||||||
|
|
||||||
|
//compute total price
|
||||||
|
var total = 0.0;
|
||||||
|
for (p in order.products) {
|
||||||
|
var pinfo = products.get(p.productId);
|
||||||
|
if (pinfo == null) continue;
|
||||||
|
total += p.quantity * pinfo.price;
|
||||||
|
}
|
||||||
|
var ffilter = new sugoi.form.filters.FloatFilter();
|
||||||
|
|
||||||
|
var total = ffilter.filterString(Std.string(App.roundTo(total,2)));
|
||||||
|
c.append("<div class='total'>TOTAL : " + total + "</div>");
|
||||||
|
|
||||||
|
|
||||||
|
if (order.products.length > 0){
|
||||||
|
App.instance.setWarningOnUnload(true,"Vous avez une commande en cours. Si vous quittez cette page sans confirmer, votre commande sera perdue.");
|
||||||
|
}else{
|
||||||
|
App.instance.setWarningOnUnload(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function findCategoryName(cid:Int):String{
|
||||||
|
|
||||||
|
for ( cg in this.categories ){
|
||||||
|
for (c in cg.categs){
|
||||||
|
if (cid == c.id) {
|
||||||
|
return c.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( cg in this.pinnedCategories ){
|
||||||
|
for (c in cg.categs){
|
||||||
|
if (cid == c.id) {
|
||||||
|
return c.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically sort products by categories
|
||||||
|
*/
|
||||||
|
public function sortProductsBy(){
|
||||||
|
|
||||||
|
//store products by groups
|
||||||
|
var groups = new Map<Int,{name:String,products:Array<ProductInfo>}>();
|
||||||
|
var pinned = new Map<Int,{name:String,products:Array<ProductInfo>}>();
|
||||||
|
|
||||||
|
var firstCategGroup = this.categories[0].categs;
|
||||||
|
|
||||||
|
//trace(firstCategGroup);
|
||||||
|
//trace(pinnedCategories);
|
||||||
|
|
||||||
|
var pList = this.productsArray.copy();
|
||||||
|
|
||||||
|
//for ( p in pList) trace(p.name+" : " + p.categories);
|
||||||
|
//trace("----------------");
|
||||||
|
|
||||||
|
//sort by categs
|
||||||
|
for ( p in pList.copy() ){
|
||||||
|
//trace(p.name+" : " + p.categories);
|
||||||
|
untyped p.element.remove();
|
||||||
|
|
||||||
|
for ( categ in p.categories){
|
||||||
|
|
||||||
|
if (Lambda.find(firstCategGroup, function(c) return c.id == categ) != null){
|
||||||
|
|
||||||
|
//is in this category group
|
||||||
|
var g = groups.get(categ);
|
||||||
|
if ( g == null){
|
||||||
|
var name = findCategoryName(categ);
|
||||||
|
g = {name:name,products:[]};
|
||||||
|
}
|
||||||
|
g.products.push(p);
|
||||||
|
//trace("remove " + p.name);
|
||||||
|
pList.remove(p);
|
||||||
|
groups.set(categ, g);
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// is in pinned group ?
|
||||||
|
var isInPinnedCateg = false;
|
||||||
|
for ( cg in pinnedCategories){
|
||||||
|
if (Lambda.find(cg.categs, function(c) return c.id == categ) != null){
|
||||||
|
isInPinnedCateg = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInPinnedCateg){
|
||||||
|
|
||||||
|
var c = pinned.get(categ);
|
||||||
|
if ( c == null){
|
||||||
|
|
||||||
|
var name = findCategoryName(categ);
|
||||||
|
c = {name:name,products:[]};
|
||||||
|
}
|
||||||
|
c.products.push(p);
|
||||||
|
//trace( "add " + p.name+" in PINNED");
|
||||||
|
pList.remove(p);
|
||||||
|
pinned.set(categ, c);
|
||||||
|
|
||||||
|
|
||||||
|
}else{
|
||||||
|
//not in the selected categ nor in pinned groups
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//if some untagged products remain
|
||||||
|
if (pList.length > 0){
|
||||||
|
groups.set(0,{name:"Autres",products:pList});
|
||||||
|
}
|
||||||
|
//trace("----------------");
|
||||||
|
//render
|
||||||
|
var container = App.j(".shop .body");
|
||||||
|
//render firts "pinned" groups , then "groups"
|
||||||
|
for ( source in [pinned, groups]){
|
||||||
|
|
||||||
|
for (o in source){
|
||||||
|
|
||||||
|
if (o.products.length == 0) continue;
|
||||||
|
container.append("<div class='col-md-12 col-xs-12 col-sm-12 col-lg-12'><div class='catHeader'>" + o.name + "</div></div>");
|
||||||
|
for ( p in o.products){
|
||||||
|
//trace("GROUP "+o.name+" : "+p.name);
|
||||||
|
//if the element has already been inserted, we need to clone it
|
||||||
|
if (untyped p.element.parent().length == 0){
|
||||||
|
container.append( untyped p.element );
|
||||||
|
}else{
|
||||||
|
var clone = untyped p.element.clone();
|
||||||
|
container.append( clone );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
App.j(".product").show();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is shopping cart empty ?
|
||||||
|
*/
|
||||||
|
public function isEmpty(){
|
||||||
|
return order.products.length == 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* submit cart
|
||||||
|
*/
|
||||||
|
public function submit() {
|
||||||
|
|
||||||
|
var req = new haxe.Http("/shop/submit");
|
||||||
|
req.onData = function(d) {
|
||||||
|
App.instance.setWarningOnUnload(false);
|
||||||
|
js.Browser.location.href = "/shop/validate/"+place+"/"+date;
|
||||||
|
|
||||||
|
}
|
||||||
|
req.addParameter("data", haxe.Json.stringify(order));
|
||||||
|
req.request(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filter products by category
|
||||||
|
*/
|
||||||
|
public function filter(cat:Int) {
|
||||||
|
|
||||||
|
//icone sur bouton
|
||||||
|
App.j(".tag").removeClass("active").children().remove("span");//clean
|
||||||
|
|
||||||
|
var bt = App.j("#tag" + cat);
|
||||||
|
bt.addClass("active").prepend("<span class ='glyphicon glyphicon-ok'></span> ");
|
||||||
|
|
||||||
|
|
||||||
|
//affiche/masque produits
|
||||||
|
for (p in products) {
|
||||||
|
if (cat==0 || Lambda.has(p.categories, cat)) {
|
||||||
|
App.j(".shop .product" + p.id).fadeIn(300);
|
||||||
|
}else {
|
||||||
|
App.j(".shop .product" + p.id).fadeOut(300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove a product from cart
|
||||||
|
* @param pid
|
||||||
|
*/
|
||||||
|
public function remove(pid:Int ) {
|
||||||
|
|
||||||
|
loader.show();
|
||||||
|
|
||||||
|
//add server side
|
||||||
|
var r = new haxe.Http('/shop/remove/$pid');
|
||||||
|
|
||||||
|
r.onData = function(data:String) {
|
||||||
|
|
||||||
|
loader.hide();
|
||||||
|
|
||||||
|
var d = haxe.Json.parse(data);
|
||||||
|
if (!d.success) js.Browser.alert("Erreur : "+d);
|
||||||
|
|
||||||
|
//remove locally
|
||||||
|
for ( p in order.products.copy()) {
|
||||||
|
if (p.productId == pid) {
|
||||||
|
order.products.remove(p);
|
||||||
|
render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
r.request();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* loads products DB and existing cart in ajax
|
||||||
|
*/
|
||||||
|
public function init(place:Int,date:String) {
|
||||||
|
|
||||||
|
this.place = place;
|
||||||
|
this.date = date;
|
||||||
|
|
||||||
|
loader = App.j("#cartContainer #loader");
|
||||||
|
|
||||||
|
var req = new haxe.Http("/shop/init/"+place+"/"+date);
|
||||||
|
req.onData = function(data) {
|
||||||
|
loader.hide();
|
||||||
|
|
||||||
|
var data : {
|
||||||
|
products:Array<ProductInfo>,
|
||||||
|
categories:Array<{name:String,pinned:Bool,categs:Array<CategoryInfo>}>,
|
||||||
|
order:OrderInSession } = haxe.Unserializer.run(data);
|
||||||
|
|
||||||
|
//populate local categories lists
|
||||||
|
for ( cg in data.categories){
|
||||||
|
if (cg.pinned){
|
||||||
|
pinnedCategories.push(cg);
|
||||||
|
}else{
|
||||||
|
categories.push(cg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//product DB
|
||||||
|
for (p in data.products) {
|
||||||
|
//catch dom element for further usage
|
||||||
|
untyped p.element = App.j(".product"+p.id);
|
||||||
|
|
||||||
|
var id : Int = p.id;
|
||||||
|
//var id : Int = p.id;
|
||||||
|
//id = id + 1;
|
||||||
|
this.products.set(id, p);
|
||||||
|
this.productsArray.push(p);
|
||||||
|
//trace(p.name+" : " + p.categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
//existing order
|
||||||
|
for ( p in data.order.products) {
|
||||||
|
subAdd(p.productId,p.quantity );
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
sortProductsBy();
|
||||||
|
|
||||||
|
}
|
||||||
|
req.request();
|
||||||
|
|
||||||
|
//DISABLED : pb quand le panier est plus haut que l'ecran
|
||||||
|
//scroll mgmt, only for large screens. Otherwise let the cart on page bottom.
|
||||||
|
/*if (js.Browser.window.matchMedia("(min-width: 1024px)").matches) {
|
||||||
|
|
||||||
|
jWindow = App.j(js.Browser.window);
|
||||||
|
cartContainer = App.j("#cartContainer");
|
||||||
|
cartTop = cartContainer.position().top;
|
||||||
|
cartLeft = cartContainer.position().left;
|
||||||
|
cartWidth = cartContainer.width();
|
||||||
|
jWindow.scroll(onScroll);
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* keep the cart on top when scrolling
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
public function onScroll(e:Dynamic) {
|
||||||
|
|
||||||
|
//cart container top position
|
||||||
|
|
||||||
|
if (jWindow.scrollTop() > cartTop) {
|
||||||
|
//trace("absolute !");
|
||||||
|
cartContainer.addClass("scrolled");
|
||||||
|
cartContainer.css('left', Std.string(cartLeft) + "px");
|
||||||
|
cartContainer.css('top', Std.string(/*cartTop*/10) + "px");
|
||||||
|
cartContainer.css('width', Std.string(cartWidth) + "px");
|
||||||
|
|
||||||
|
}else {
|
||||||
|
cartContainer.removeClass("scrolled");
|
||||||
|
cartContainer.css('left',"");
|
||||||
|
cartContainer.css('top', "");
|
||||||
|
cartContainer.css('width', "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Executable
+151
@@ -0,0 +1,151 @@
|
|||||||
|
package ;
|
||||||
|
import Common;
|
||||||
|
import js.JQuery;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Tag products with categories
|
||||||
|
*
|
||||||
|
* @author fbarbut<francois.barbut@gmail.com>
|
||||||
|
*/
|
||||||
|
@:keep
|
||||||
|
class Tagger
|
||||||
|
{
|
||||||
|
|
||||||
|
var contractId : Int;
|
||||||
|
var data:TaggerInfos;
|
||||||
|
var pids : Array<Int>; //selected product Ids
|
||||||
|
|
||||||
|
public function new(cid:Int)
|
||||||
|
{
|
||||||
|
contractId = cid;
|
||||||
|
pids = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init() {
|
||||||
|
var req = new haxe.Http("/product/categorizeInit/"+contractId);
|
||||||
|
req.onData = function(_data) {
|
||||||
|
data = haxe.Json.parse(_data);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
req.request();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
|
||||||
|
var html = new StringBuf();
|
||||||
|
|
||||||
|
html.add("<table class='table'>");
|
||||||
|
for (p in data.products) {
|
||||||
|
html.add("<tr class='p"+p.product.id+"'>");
|
||||||
|
var checked = Lambda.has(pids,p.product.id) ? "checked" : "";
|
||||||
|
html.add('<td><input type="checkbox" name="p${p.product.id}" $checked/></td>');
|
||||||
|
html.add("<td>" + p.product.name+"</td>");
|
||||||
|
var tags = [];
|
||||||
|
|
||||||
|
//trace('product tags ${p.categories} from tags ${data.categories}');
|
||||||
|
|
||||||
|
for (c in p.categories) {
|
||||||
|
//trouve le nom du tag
|
||||||
|
var name = "";
|
||||||
|
var color = "";
|
||||||
|
for (gc in data.categories) {
|
||||||
|
for ( t in gc.tags) {
|
||||||
|
if (c == t.id) {
|
||||||
|
name = t.name;
|
||||||
|
color = gc.color;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//var bt = App.jq("<a>[X]</a>")
|
||||||
|
tags.push("<span class='tag t"+c+"' style='background-color:"+color+";cursor:pointer;'>"+name+"</span>");
|
||||||
|
}
|
||||||
|
|
||||||
|
html.add("<td class='tags'>"+ tags.join(" ") +"</td>");
|
||||||
|
html.add("</tr>");
|
||||||
|
}
|
||||||
|
html.add("</table>");
|
||||||
|
App.jq("#tagger").html(html.toString());
|
||||||
|
App.jq("#tagger .tag").click(function(e) {
|
||||||
|
|
||||||
|
var el : js.html.Element = cast e.currentTarget;
|
||||||
|
|
||||||
|
//find tag Id
|
||||||
|
var tid = Std.parseInt(el.getAttribute('class').split(" ")[1].substr(1));
|
||||||
|
|
||||||
|
//find product Id
|
||||||
|
var pid = Std.parseInt(el.parentElement.parentElement.getAttribute('class').substr(1));
|
||||||
|
|
||||||
|
//remove element
|
||||||
|
el.remove();
|
||||||
|
|
||||||
|
//datas
|
||||||
|
remove(tid,pid);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add() {
|
||||||
|
var tagId = Std.parseInt(App.jq("#tag").val());
|
||||||
|
|
||||||
|
if (tagId == 0) js.Browser.alert("Impossible de trouver la catégorie selectionnée");
|
||||||
|
|
||||||
|
pids = [];
|
||||||
|
for ( e in App.jq("#tagger input:checked").elements() ) {
|
||||||
|
pids.push(Std.parseInt(e.attr("name").substr(1)));
|
||||||
|
}
|
||||||
|
if (pids.length == 0) js.Browser.alert("Sélectionnez un produit afin de pouvoir lui attribuer une catégorie");
|
||||||
|
|
||||||
|
for (p in pids) {
|
||||||
|
addTag(tagId, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(tagId:Int,productId:Int) {
|
||||||
|
//data
|
||||||
|
for ( p in data.products) {
|
||||||
|
if (p.product.id == productId) {
|
||||||
|
for ( t in p.categories) {
|
||||||
|
if (t == tagId) p.categories.remove(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTag(tagId:Int, productId:Int) {
|
||||||
|
|
||||||
|
//check for doubles
|
||||||
|
for ( p in data.products) {
|
||||||
|
if (p.product.id == productId) {
|
||||||
|
for (t in p.categories) {
|
||||||
|
if (t == tagId) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//data
|
||||||
|
for ( p in data.products) {
|
||||||
|
if (p.product.id == productId) {
|
||||||
|
p.categories.push(tagId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit() {
|
||||||
|
|
||||||
|
var req = new haxe.Http("/product/categorizeSubmit/" + contractId);
|
||||||
|
req.addParameter("data", haxe.Json.stringify(data));
|
||||||
|
req.onData = function(_data) {
|
||||||
|
|
||||||
|
js.Browser.alert(_data);
|
||||||
|
}
|
||||||
|
req.request(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
package;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tutorial javascript widget
|
||||||
|
* @author fbarbut<francois.barbut@gmail.com>
|
||||||
|
*/
|
||||||
|
class Tuto
|
||||||
|
{
|
||||||
|
var name:String;
|
||||||
|
var step:Int;
|
||||||
|
|
||||||
|
static var LAST_ELEMENT :String = null; //last hightlit element
|
||||||
|
|
||||||
|
public function new(name:String, step:Int){
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.step = step;
|
||||||
|
|
||||||
|
TutoDatas.get(name, init);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asyn init
|
||||||
|
* @param tuto
|
||||||
|
*/
|
||||||
|
function init(tuto)
|
||||||
|
{
|
||||||
|
|
||||||
|
var s = tuto.steps[step];
|
||||||
|
|
||||||
|
//close previous popovers
|
||||||
|
var p = App.jq(".popover");
|
||||||
|
untyped p.popover('hide');
|
||||||
|
|
||||||
|
var t = App.instance.t;
|
||||||
|
if (t == null) trace("Gettext translator is null");
|
||||||
|
|
||||||
|
if (s == null) {
|
||||||
|
/**
|
||||||
|
* tutorial is finished : display a modal window
|
||||||
|
*/
|
||||||
|
var m = App.jq('#myModal');
|
||||||
|
untyped m.modal('show');
|
||||||
|
m.addClass("help");
|
||||||
|
m.find(".modal-header").html("<span class='glyphicon glyphicon-hand-right'></span> "+tuto.name);
|
||||||
|
m.find(".modal-body").html("<span class='glyphicon glyphicon-ok'></span> "+t._("This tutorial is over."));
|
||||||
|
var bt = App.jq("<a class='btn btn-default'><span class='glyphicon glyphicon-chevron-right'></span> "+t._("Come back to tutorials page")+"</a>");
|
||||||
|
bt.click(function(?_) {
|
||||||
|
untyped m.modal('hide');
|
||||||
|
js.Browser.location.href = "/contract?stopTuto=1";
|
||||||
|
});
|
||||||
|
m.find(".modal-footer").append(bt);
|
||||||
|
m.find(".modal-dialog").removeClass("modal-lg"); //small window pls
|
||||||
|
|
||||||
|
}else if (s.element == null) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* no element, make a modal window (usually its the first step of the tutorial)
|
||||||
|
*/
|
||||||
|
var m = App.jq('#myModal');
|
||||||
|
untyped m.modal('show');
|
||||||
|
m.addClass("help");
|
||||||
|
m.find(".modal-body").html(s.text);
|
||||||
|
m.find(".modal-header").html("<span class='glyphicon glyphicon-hand-right'></span> "+tuto.name);
|
||||||
|
|
||||||
|
var bt = App.jq("<a class='btn btn-default'><span class='glyphicon glyphicon-chevron-right'></span> "+t._("OK")+"</a>");
|
||||||
|
bt.click(function(?_) {
|
||||||
|
untyped m.modal('hide');
|
||||||
|
new Tuto(name, step + 1);
|
||||||
|
});
|
||||||
|
m.find(".modal-footer").append(bt);
|
||||||
|
m.find(".modal-dialog").removeClass("modal-lg"); //small window pls
|
||||||
|
|
||||||
|
}else {
|
||||||
|
|
||||||
|
//prepare Bootstrap "popover"
|
||||||
|
var x = App.jq(s.element).first().attr("title", tuto.name+" <div class='pull-right'>"+(step+1)+"/"+tuto.steps.length+"</div>");
|
||||||
|
var text = "<p>" + s.text + "</p>";
|
||||||
|
var bt = null;
|
||||||
|
switch(s.action) {
|
||||||
|
case TANext :
|
||||||
|
|
||||||
|
bt = App.jq("<p><a class='btn btn-default btn-sm'><span class='glyphicon glyphicon-chevron-right'></span> "+t._("Next")+"</a></p>");
|
||||||
|
bt.click(function(?_) {
|
||||||
|
//untyped m.modal('hide');
|
||||||
|
new Tuto(name, step + 1);
|
||||||
|
if(LAST_ELEMENT!=null) App.jq(s.element).removeClass("highlight");
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
//configure and open popover
|
||||||
|
var p = switch(s.placement) {
|
||||||
|
case TPTop: "top";
|
||||||
|
case TPBottom : "bottom";
|
||||||
|
case TPLeft : "left";
|
||||||
|
case TPRight : "right";
|
||||||
|
default : null;
|
||||||
|
}
|
||||||
|
var options = { container:"body", content:text, html:true , placement:p};
|
||||||
|
untyped x.popover(options).popover('show');
|
||||||
|
|
||||||
|
|
||||||
|
//add a footer
|
||||||
|
var footer = App.jq("<div class='footer'><div class='pull-left'></div><div class='pull-right'></div></div>");
|
||||||
|
|
||||||
|
if (bt != null) footer.find(".pull-right").append(bt);
|
||||||
|
footer.find(".pull-left").append(makeCloseButton(t._('Stop')));
|
||||||
|
|
||||||
|
App.jq(".popover .popover-content").append(footer);
|
||||||
|
|
||||||
|
//highlight
|
||||||
|
App.jq(s.element).first().addClass("highlight");
|
||||||
|
LAST_ELEMENT = s.element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a "close" bt
|
||||||
|
*/
|
||||||
|
function makeCloseButton(?text) {
|
||||||
|
var bt = App.jq("<a class='btn btn-default btn-sm'><span class='glyphicon glyphicon-remove'></span> "+(text==null?"":text)+"</a>");
|
||||||
|
bt.click(function(?_) {
|
||||||
|
js.Browser.location.href = "/contract?stopTuto=1";
|
||||||
|
});
|
||||||
|
return bt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// npm dependencies library
|
||||||
|
//
|
||||||
|
(function(scope) {
|
||||||
|
'use-strict';
|
||||||
|
scope.__registry__ = Object.assign({}, scope.__registry__, {
|
||||||
|
//
|
||||||
|
// list npm modules required in Haxe
|
||||||
|
//
|
||||||
|
'bootstrap': require('bootstrap'),
|
||||||
|
'react': require('react'),
|
||||||
|
'react-dom': require('react-dom'),
|
||||||
|
'react-bootstrap-typeahead': require('react-bootstrap-typeahead'),
|
||||||
|
'react-datetime': require('react-datetime'),
|
||||||
|
'leaflet': require('leaflet'),
|
||||||
|
'react-leaflet': require('react-leaflet'),
|
||||||
|
'react-places-autocomplete': require('react-places-autocomplete'),
|
||||||
|
'geolib': require('geolib'),
|
||||||
|
'react-router':require('react-router'),
|
||||||
|
'react-router-dom':require('react-router-dom'),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
// enable React hot-reload
|
||||||
|
require('haxe-modular');
|
||||||
|
}
|
||||||
|
|
||||||
|
})(typeof $hx_scope != "undefined" ? $hx_scope : $hx_scope = {});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package react;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @doc https://www.npmjs.com/package/react-bootstrap-datetimepicker
|
||||||
|
*/
|
||||||
|
@:jsRequire('react-bootstrap-datetimepicker')
|
||||||
|
extern class DateTimeField extends react.ReactComponent {}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package react;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Error Div
|
||||||
|
*/
|
||||||
|
class Error extends react.ReactComponentOfProps<{error:String}>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new(props:Dynamic)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
|
||||||
|
if (props.error == null) return null;
|
||||||
|
|
||||||
|
return jsx('<div className="alert alert-danger">
|
||||||
|
<span className="glyphicon glyphicon-exclamation-sign"></span> ${props.error}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package react;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message Div
|
||||||
|
*/
|
||||||
|
class Message extends react.ReactComponentOfProps<{message:String}>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new(props:Dynamic)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
|
||||||
|
if (props.message == null) return null;
|
||||||
|
|
||||||
|
return jsx('<div className="alert alert-warning">
|
||||||
|
<span className="glyphicon glyphicon glyphicon-info-sign"></span> ${props.message}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package react;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
//datepicker broken if called like this //import react.DateTimeField.*;
|
||||||
|
//@:jsRequire('react-bootstrap-datetimepicker')
|
||||||
|
//extern class DateTimeField extends react.ReactComponent {}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @doc https://github.com/YouCanBookMe/react-datetime
|
||||||
|
*/
|
||||||
|
@:jsRequire('react-datetime')
|
||||||
|
extern class DateTime extends react.ReactComponent {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ...
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class ReportHeader extends react.ReactComponentOfState<OrdersReportOptions>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
state = {startDate:null, endDate:null, groupBy:null, contracts:[]};
|
||||||
|
|
||||||
|
//load fr locale of moment.js
|
||||||
|
var moment = js.Lib.require('moment');
|
||||||
|
js.Lib.require('moment/locale/fr');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
|
||||||
|
return jsx('<div className="reportHeader">
|
||||||
|
<div className="col-md-3">
|
||||||
|
<div className="input-group">
|
||||||
|
<span className="input-group-addon">
|
||||||
|
<span className="glyphicon glyphicon-calendar"></span>
|
||||||
|
</span>
|
||||||
|
<DateTime name="startDate_PROUT" onChange={onDateChange} locale="fr" dateFormat="LLLL" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-3">
|
||||||
|
<DateTime name="endDate" onChange={onDateChange} inputFormat="YYYY-MM-DD HH:mm:ss" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-3">
|
||||||
|
<select className="form-control" onChange={onGroupByChange}>
|
||||||
|
<option value="ByMember">Par adhérent</option>
|
||||||
|
<option value="ByProduct">Par Produit</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-3">
|
||||||
|
<a className="btn btn-primary">Afficher</a>
|
||||||
|
</div>
|
||||||
|
</div>');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDateChange(e:js.html.Event){
|
||||||
|
trace("onDateChange");
|
||||||
|
//var name :String = untyped e.target.name;
|
||||||
|
//var value :String = untyped e.target.value;
|
||||||
|
//trace('$name $value');
|
||||||
|
trace(e);
|
||||||
|
//e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @doc https://facebook.github.io/react/docs/forms.html
|
||||||
|
*/
|
||||||
|
function onGroupByChange(e:js.html.Event){
|
||||||
|
e.preventDefault();
|
||||||
|
trace("onGRoupByChange");
|
||||||
|
var name :String = untyped e.target.name;
|
||||||
|
var value :String = untyped e.target.value;
|
||||||
|
if (value == "ByMember"){
|
||||||
|
state.groupBy = ByMember;
|
||||||
|
}else{
|
||||||
|
state.groupBy = ByProduct;
|
||||||
|
}
|
||||||
|
trace(state);
|
||||||
|
setState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package react;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Use the 1.4.x version. The 2.x version is still buggy
|
||||||
|
*
|
||||||
|
* @doc https://www.npmjs.com/package/react-bootstrap-typeahead
|
||||||
|
*/
|
||||||
|
@:jsRequire('react-bootstrap-typeahead', 'Typeahead')
|
||||||
|
extern class Typeahead extends react.ReactComponent{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @doc https://github.com/ericgio/react-bootstrap-typeahead/blob/803f61c1c8d0c943106233ed3c9306acc19b5b2b/docs/API.md#asynctypeahead
|
||||||
|
* Async component is needed when options and searches are managed asynchronously
|
||||||
|
*/
|
||||||
|
@:jsRequire('react-bootstrap-typeahead', 'AsyncTypeahead')
|
||||||
|
extern class AsyncTypeahead extends react.ReactComponent{}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package react;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
// I need to store also the "input" because of https://stackoverflow.com/questions/29140354/how-to-handle-decimal-values-in-reacts-onchange-event-for-input
|
||||||
|
typedef VATBoxState = {ht:Float, ttc:Float, vat:Float, htInput:String, ttcInput:String,lastEdited:String};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A box to manage prices with and without VAT
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class VATBox extends react.ReactComponentOfPropsAndState<{ttc:Float,currency:String,vatRates:String,vat:Float,formName:String},VATBoxState>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
//trace(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
ht : round(props.ttc/(1+props.vat/100)),
|
||||||
|
htInput : Std.string(round(props.ttc/(1+props.vat/100))),
|
||||||
|
ttc : round(props.ttc),
|
||||||
|
ttcInput : Std.string(round(props.ttc)),
|
||||||
|
vat:props.vat,
|
||||||
|
lastEdited:null
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
|
||||||
|
var rates :Array<Float>= props.vatRates.split("|").map(Std.parseFloat);
|
||||||
|
|
||||||
|
var options = [for (r in rates) jsx('<option key="$r" value="$r">$r %</option>') ];
|
||||||
|
var priceInputName = props.formName+"_price";
|
||||||
|
var vatInputName = props.formName+"_vat";
|
||||||
|
|
||||||
|
return jsx('<div>
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-4 text-center"> Hors taxe </div>
|
||||||
|
<div className="col-md-4 text-center"> Taux de TVA </div>
|
||||||
|
<div className="col-md-4 text-center"> TTC </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-4">
|
||||||
|
<div className="input-group">
|
||||||
|
<input type="text" name="htInput" value="${state.htInput}" className="form-control" onChange={onChange}/>
|
||||||
|
<div className="input-group-addon">${props.currency}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-4">
|
||||||
|
<select name="vat" className="form-control" onChange={onChange} defaultValue=${state.vat}>
|
||||||
|
${options}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-4">
|
||||||
|
<div className="input-group">
|
||||||
|
<input type="text" name="ttcInput" value="${state.ttcInput}" className="form-control" onChange={onChange}/>
|
||||||
|
<div className="input-group-addon">${props.currency}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="$priceInputName" value="${state.ttc}" />
|
||||||
|
<input type="hidden" name="$vatInputName" value="${state.vat}" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute prices
|
||||||
|
*/
|
||||||
|
function onChange(e:js.html.Event){
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
var name :String = untyped e.target.name;
|
||||||
|
var input : String = Std.string(untyped e.target.value);
|
||||||
|
if (input == null || input == "") input = "0";
|
||||||
|
input = StringTools.replace(input, ",", ".");
|
||||||
|
var value : Float = Std.parseFloat(input);
|
||||||
|
if (value == null) value = 0;
|
||||||
|
|
||||||
|
var rate = 1 + (state.vat / 100);
|
||||||
|
//trace('name:$name - raw:' + untyped e.target.value+' - input:$input - value:$value ');
|
||||||
|
|
||||||
|
switch(name){
|
||||||
|
case "htInput":
|
||||||
|
|
||||||
|
this.setState(cast {ht:value , htInput:input , ttc: round(value * rate), ttcInput:round(value * rate) , lastEdited:"htInput"});
|
||||||
|
|
||||||
|
case "ttcInput":
|
||||||
|
this.setState(cast {ht: round(value / rate), htInput : round(value/rate), ttcInput:input , ttc:value , lastEdited:"ttcInput"});
|
||||||
|
|
||||||
|
case "vat":
|
||||||
|
rate = 1 + (value / 100);
|
||||||
|
if (state.lastEdited == "htInput"){
|
||||||
|
//compute ttc from ht
|
||||||
|
this.setState(cast { vat:value, ht:state.ht, htInput:state.ht, ttc:round(state.ht * rate) , ttcInput:round(state.ht * rate)} );
|
||||||
|
}else{
|
||||||
|
//compute ht from ttc
|
||||||
|
this.setState(cast { vat:value, ht: round( state.ttc/rate ), htInput: round( state.ttc/rate ), ttc:state.ttc , ttcInput:state.ttc} );
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
inline function round(f:Float):Float{
|
||||||
|
return Math.round(f * 100) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
package react.map;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
import leaflet.L;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Externs for react-leaflet
|
||||||
|
* @doc https://react-leaflet.js.org/docs/en/intro.html
|
||||||
|
*/
|
||||||
|
@:jsRequire('react-leaflet', 'Map')
|
||||||
|
extern class LeafMap extends ReactComponent {}
|
||||||
|
@:jsRequire('react-leaflet', 'TileLayer')
|
||||||
|
extern class TileLayer extends ReactComponent {}
|
||||||
|
@:jsRequire('react-leaflet', 'Marker')
|
||||||
|
extern class Marker extends ReactComponent {}
|
||||||
|
@:jsRequire('react-leaflet', 'CircleMarker')
|
||||||
|
extern class CircleMarker extends ReactComponent {}
|
||||||
|
@:jsRequire('react-leaflet', 'Popup')
|
||||||
|
extern class Popup extends ReactComponent {}
|
||||||
|
@:jsRequire('react-leaflet', 'FeatureGroup')
|
||||||
|
extern class FeatureGroup extends ReactComponent {}
|
||||||
|
@:jsRequire('react-leaflet', 'LayerGroup')
|
||||||
|
extern class LayerGroup extends ReactComponent {}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
extern class L2 {
|
||||||
|
static function icon(a:Dynamic):Dynamic;
|
||||||
|
static function latLng(lat:Float, lng:Float):Dynamic;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GroupItem
|
||||||
|
* @author rcrestey
|
||||||
|
*/
|
||||||
|
typedef GroupMapProps = {
|
||||||
|
var addressCoord:Dynamic;
|
||||||
|
var groups:Array<GroupOnMap>;
|
||||||
|
var fetchGroupsInsideBox:Box->Void;
|
||||||
|
var groupFocusedId:Int;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef GroupMapState = {
|
||||||
|
var isFitting:Bool;
|
||||||
|
var focusedMarker:Dynamic;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Box = {
|
||||||
|
var minLat:Float;
|
||||||
|
var maxLat:Float;
|
||||||
|
var minLng:Float;
|
||||||
|
var maxLng:Float;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupMap extends ReactComponentOfPropsAndState<GroupMapProps, GroupMapState> {
|
||||||
|
static inline var DEFAULT_LAT = 46.52863469527167; // center of France
|
||||||
|
static inline var DEFAULT_LNG = 2.43896484375; // center of France
|
||||||
|
static inline var INIT_ZOOM = 6;
|
||||||
|
static inline var DEFAULT_ZOOM = 13;
|
||||||
|
|
||||||
|
var map:Dynamic;
|
||||||
|
var featureGroup:Dynamic;
|
||||||
|
var markerMap = new Map<Int,Dynamic>();
|
||||||
|
|
||||||
|
var groupIcon = L.icon({
|
||||||
|
iconUrl: '/img/marker.svg',
|
||||||
|
iconSize: [40, 40],
|
||||||
|
iconAnchor: [20, 40],
|
||||||
|
popupAnchor: [0, -30],
|
||||||
|
className: 'icon'
|
||||||
|
});
|
||||||
|
|
||||||
|
var homeIcon = L.icon({
|
||||||
|
iconUrl: '/img/home.svg',
|
||||||
|
iconSize: [40, 40],
|
||||||
|
iconAnchor: [20, 20],
|
||||||
|
popupAnchor: [0, -30],
|
||||||
|
className: 'icon'
|
||||||
|
});
|
||||||
|
|
||||||
|
function new() {
|
||||||
|
super();
|
||||||
|
state = {
|
||||||
|
isFitting: false,
|
||||||
|
focusedMarker: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMap(element:Dynamic):Void {
|
||||||
|
map = element.leafletElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFeatureGroup(element:Dynamic):Void {
|
||||||
|
featureGroup = element.leafletElement;
|
||||||
|
setState({
|
||||||
|
isFitting: true
|
||||||
|
}, fitBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarker(element:Dynamic, id:Int):Void {
|
||||||
|
if (element != null)
|
||||||
|
markerMap.set(id, element.leafletElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call API to get groups in the current bounding box
|
||||||
|
*/
|
||||||
|
function getGroups() {
|
||||||
|
var bounds = map.getBounds();
|
||||||
|
var southWest = bounds.getSouthWest();
|
||||||
|
var northEast = bounds.getNorthEast();
|
||||||
|
|
||||||
|
props.fetchGroupsInsideBox({
|
||||||
|
minLat: southWest.lat,
|
||||||
|
maxLat: northEast.lat,
|
||||||
|
minLng: southWest.lng,
|
||||||
|
maxLng: northEast.lng
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitBounds() {
|
||||||
|
map.fitBounds(featureGroup.getBounds(), {
|
||||||
|
padding: [30, 30]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMoveEnd() {
|
||||||
|
if (
|
||||||
|
props.addressCoord != null &&
|
||||||
|
!Lambda.empty(props.groups) &&
|
||||||
|
map.distance(map.getCenter(), props.addressCoord) == 0
|
||||||
|
)
|
||||||
|
setState({
|
||||||
|
isFitting: true
|
||||||
|
}, fitBounds);
|
||||||
|
else if (state.isFitting)
|
||||||
|
setState({
|
||||||
|
isFitting: false
|
||||||
|
});
|
||||||
|
else
|
||||||
|
getGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function componentDidMount() {
|
||||||
|
if (props.addressCoord == null)
|
||||||
|
getGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function shouldComponentUpdate(nextProps:GroupMapProps, nextState:GroupMapState) {
|
||||||
|
if (nextState.focusedMarker != state.focusedMarker)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function componentDidUpdate(prevProps:GroupMapProps, prevState:GroupMapState) {
|
||||||
|
if (props.groupFocusedId != null) {
|
||||||
|
if (
|
||||||
|
prevProps.groupFocusedId != props.groupFocusedId
|
||||||
|
|| state.focusedMarker == null
|
||||||
|
) {
|
||||||
|
if (state.focusedMarker != null)
|
||||||
|
state.focusedMarker.closePopup();
|
||||||
|
|
||||||
|
var focusedMarker = markerMap.get(props.groupFocusedId);
|
||||||
|
focusedMarker.openPopup();
|
||||||
|
|
||||||
|
setState({
|
||||||
|
focusedMarker: focusedMarker
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (prevProps.groupFocusedId != null && state.focusedMarker != null) {
|
||||||
|
state.focusedMarker.closePopup();
|
||||||
|
|
||||||
|
setState({
|
||||||
|
focusedMarker: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render() {
|
||||||
|
var center = props.addressCoord == null
|
||||||
|
? L.latLng(DEFAULT_LAT, DEFAULT_LNG)
|
||||||
|
: props.addressCoord;
|
||||||
|
|
||||||
|
var zoom = props.addressCoord == null
|
||||||
|
? INIT_ZOOM
|
||||||
|
: DEFAULT_ZOOM;
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<LeafMap
|
||||||
|
center=${center}
|
||||||
|
zoom=${zoom}
|
||||||
|
ref=${getMap}
|
||||||
|
onMoveEnd=${handleMoveEnd}
|
||||||
|
>
|
||||||
|
<TileLayer
|
||||||
|
attribution="&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
|
||||||
|
url="https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiYnViYXIiLCJhIjoiY2loM2lubmZpMDBwcGtxbHlwdmw0bXRkbCJ9.rfgXPakoGnXZ3wIGA3-1kQ"
|
||||||
|
id="bubar.cih3inmqd00tjuxm7oc2532l0"
|
||||||
|
/>
|
||||||
|
<FeatureGroup ref=${getFeatureGroup}>
|
||||||
|
${renderGroupMarkers()}
|
||||||
|
${renderHomeMarker()}
|
||||||
|
</FeatureGroup>
|
||||||
|
</LeafMap>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGroupMarkers() {
|
||||||
|
var markers = props.groups.map(function(group) {
|
||||||
|
var coord = [group.place.latitude, group.place.longitude];
|
||||||
|
|
||||||
|
function markerGetter(e:Dynamic) {
|
||||||
|
getMarker(e, group.place.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
var image = group.image==null ? null : jsx('<img className="groupImage img-responsive" src=${group.image}/>');
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<Marker
|
||||||
|
position=${coord}
|
||||||
|
ref=${markerGetter}
|
||||||
|
key=${group.place.id}
|
||||||
|
icon=${groupIcon}
|
||||||
|
>
|
||||||
|
<Popup className="popup">
|
||||||
|
<div>
|
||||||
|
<a href=${"/group/"+group.id} target="_blank">
|
||||||
|
$image
|
||||||
|
<div className="groupName">${group.name}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</Popup>
|
||||||
|
</Marker>
|
||||||
|
');
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsx('<div>${markers}</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHomeMarker() {
|
||||||
|
if (props.addressCoord != null)
|
||||||
|
return jsx('<Marker position=${props.addressCoord} icon=${homeIcon} />');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
package react.map;
|
||||||
|
import js.Promise;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import utils.HttpUtil;
|
||||||
|
import leaflet.L;
|
||||||
|
import Common;
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
@:jsRequire('react-places-autocomplete', 'default')
|
||||||
|
extern class Autocomplete extends ReactComponent {}
|
||||||
|
|
||||||
|
@:jsRequire('react-places-autocomplete')
|
||||||
|
extern class GeoUtil {
|
||||||
|
static function geocodeByAddress(address:Dynamic):Promise<Dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:jsRequire('geolib')
|
||||||
|
extern class Geolib {
|
||||||
|
static function getDistance(start:Dynamic, end:Dynamic):Float;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups Map
|
||||||
|
* @author rcrestey
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef GroupMapRootState = {
|
||||||
|
var point:Dynamic;
|
||||||
|
var address:String;
|
||||||
|
var groups:Array<GroupOnMap>;
|
||||||
|
var groupFocusedId:Int;
|
||||||
|
var isInit:Bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef GroupMapRootProps = {
|
||||||
|
var lat:Float;
|
||||||
|
var lng:Float;
|
||||||
|
var address:String;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupMapRoot extends ReactComponentOfState<GroupMapRootState>{
|
||||||
|
|
||||||
|
static inline var GROUP_MAP_URL = '/api/group/map';
|
||||||
|
|
||||||
|
var distanceMap = new Map<Int,Dynamic>();
|
||||||
|
|
||||||
|
public function new(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
state = {
|
||||||
|
point: L.latLng(props.lat, props.lng),
|
||||||
|
address: props.address,
|
||||||
|
groups: [],
|
||||||
|
groupFocusedId: null,
|
||||||
|
isInit: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(address) {
|
||||||
|
setState({
|
||||||
|
address: address
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPopup(group:Dynamic) {
|
||||||
|
setState({
|
||||||
|
groupFocusedId: group.place.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePopup() {
|
||||||
|
setState({
|
||||||
|
groupFocusedId: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function geocodeByAddress(address:String):Promise<Dynamic> {
|
||||||
|
return GeoUtil.geocodeByAddress(address)
|
||||||
|
.then(function(results) {
|
||||||
|
var lat = results[0].geometry.location.lat();
|
||||||
|
var lng = results[0].geometry.location.lng();
|
||||||
|
|
||||||
|
return {lat: lat, lng: lng};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call API to find groups at $lat and $lng
|
||||||
|
* @param lat -
|
||||||
|
* @param lng -
|
||||||
|
*/
|
||||||
|
function fetchGroups(lat:Float, lng:Float) {
|
||||||
|
HttpUtil.fetch(GROUP_MAP_URL, GET, {lat: lat, lng: lng}, JSON)
|
||||||
|
.then(function(results) {
|
||||||
|
setState({
|
||||||
|
point: L.latLng(lat, lng),
|
||||||
|
groups: results.groups,
|
||||||
|
isInit: true
|
||||||
|
}, fillDistanceMap);
|
||||||
|
})
|
||||||
|
.catchError(function(error) {
|
||||||
|
trace('Error', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call API to look for groups in the defined bounding box
|
||||||
|
*/
|
||||||
|
var wait : Bool;
|
||||||
|
function fetchGroupsInsideBox(newBox) {
|
||||||
|
|
||||||
|
switch(wait){
|
||||||
|
case null,false : wait = true;
|
||||||
|
case true : trace("stop"); return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpUtil.fetch(GROUP_MAP_URL, GET, newBox, JSON)
|
||||||
|
.then(function(results) {
|
||||||
|
wait = false;
|
||||||
|
setState({
|
||||||
|
groups: results.groups
|
||||||
|
}, fillDistanceMap);
|
||||||
|
});
|
||||||
|
/*.catchError(function(error) {
|
||||||
|
trace('Error', error + " stack:"+haxe.CallStack.toString(haxe.CallStack.exceptionStack())) ;
|
||||||
|
wait = false;
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupDistance(group:GroupOnMap):Float {
|
||||||
|
if (state.point == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var start = {
|
||||||
|
latitude: state.point.lat,
|
||||||
|
longitude: state.point.lng
|
||||||
|
};
|
||||||
|
var end = {
|
||||||
|
latitude: group.place.latitude,
|
||||||
|
longitude: group.place.longitude
|
||||||
|
};
|
||||||
|
|
||||||
|
return Geolib.getDistance(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillDistanceMap() {
|
||||||
|
for (group in state.groups) {
|
||||||
|
distanceMap.set(group.place.id, getGroupDistance(group));
|
||||||
|
}
|
||||||
|
|
||||||
|
orderGroupsByDistance(state.groups);
|
||||||
|
|
||||||
|
setState({
|
||||||
|
groups: state.groups
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderGroupsByDistance(groups:Array<GroupOnMap>) {
|
||||||
|
groups.sort(function(a, b) {
|
||||||
|
return distanceMap.get(a.place.id) - distanceMap.get(b.place.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertDistance(distance:Int):String { // to test
|
||||||
|
if (distance > 10000)
|
||||||
|
return Math.floor(distance / 1000) + ' km';
|
||||||
|
if (distance > 1000)
|
||||||
|
return Math.floor(distance / 100) / 10 + ' km';
|
||||||
|
return distance + ' m';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(address:String) {
|
||||||
|
setState({
|
||||||
|
address: address
|
||||||
|
});
|
||||||
|
|
||||||
|
geocodeByAddress(address)
|
||||||
|
.then(function(coord) {
|
||||||
|
fetchGroups(coord.lat, coord.lng);
|
||||||
|
})
|
||||||
|
.catchError(function(error) {
|
||||||
|
trace('Error', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function componentDidMount() {
|
||||||
|
if (state.point != null)
|
||||||
|
fetchGroups(state.point.lat, state.point.lng);
|
||||||
|
else if (state.address != '')
|
||||||
|
handleSelect(state.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSuggestion(obj:Dynamic) {
|
||||||
|
return jsx('
|
||||||
|
<div className="autocomplete-item">
|
||||||
|
<i className="fa fa-map-marker autocomplete-icon" />
|
||||||
|
<strong>${obj.formattedSuggestion.mainText}</strong>
|
||||||
|
<small className="text-muted">${obj.formattedSuggestion.secondaryText}</small>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render() {
|
||||||
|
var inputProps = {
|
||||||
|
value: state.address,
|
||||||
|
onChange: onChange
|
||||||
|
};
|
||||||
|
|
||||||
|
var cssClasses = {
|
||||||
|
root: 'form-group',
|
||||||
|
input: 'autocomplete-input',
|
||||||
|
autocompleteContainer: 'autocomplete-results',
|
||||||
|
};
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="group-map">
|
||||||
|
<div className="row">
|
||||||
|
<div id="logo" className="col-md-3"> </div>
|
||||||
|
<div className="col-md-9">
|
||||||
|
<div className="form-group-container">
|
||||||
|
Trouvez un groupe Cagette près de chez vous
|
||||||
|
<Autocomplete
|
||||||
|
inputProps=${inputProps}
|
||||||
|
onSelect=${handleSelect}
|
||||||
|
classNames=${cssClasses}
|
||||||
|
renderSuggestion=${renderSuggestion}
|
||||||
|
placeHolder="Saisissez votre adresse"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-3" id="groupsContainer">${renderGroupList()}</div>
|
||||||
|
<div className="col-md-9" id="mapContainer">${renderGroupMap()}</div>
|
||||||
|
</div>
|
||||||
|
</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGroupMap() {
|
||||||
|
if (!state.isInit)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<GroupMap
|
||||||
|
addressCoord=${state.point}
|
||||||
|
groups=${state.groups}
|
||||||
|
fetchGroupsInsideBox=${fetchGroupsInsideBox}
|
||||||
|
groupFocusedId=${state.groupFocusedId}
|
||||||
|
/>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGroupList() {
|
||||||
|
var groups = state.groups.map(function(group) {
|
||||||
|
return renderGroup(group);
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="groups">
|
||||||
|
${groups}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a group in the left list
|
||||||
|
*/
|
||||||
|
function renderGroup(group:GroupOnMap) {
|
||||||
|
var address = [
|
||||||
|
group.place.address1,
|
||||||
|
group.place.address2,
|
||||||
|
[group.place.zipCode, group.place.city].join(" "),
|
||||||
|
];
|
||||||
|
|
||||||
|
var addressBlock = Lambda.array(address.mapi(function(index, element) {
|
||||||
|
if (element != null){
|
||||||
|
return jsx('<div key=${index}>$element</div>');
|
||||||
|
}else{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
var distance = null;
|
||||||
|
if (distanceMap.get(group.place.id) != null)
|
||||||
|
distance = jsx('<div className="distance">${convertDistance(distanceMap.get(group.place.id))}</div>');
|
||||||
|
|
||||||
|
var classNames = ['clickable groupBlock'];
|
||||||
|
if (group.place.id == state.groupFocusedId)
|
||||||
|
classNames.push('focused');
|
||||||
|
|
||||||
|
var img = if(group.image==null) {
|
||||||
|
null;
|
||||||
|
}else{
|
||||||
|
jsx('<img src="${group.image}" className="img-responsive" />');
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsx('<a target="_blank"
|
||||||
|
onMouseEnter=${function() { openPopup(group); }}
|
||||||
|
onMouseLeave=${closePopup}
|
||||||
|
className=${classNames.join(' ')}
|
||||||
|
key=${group.place.id}
|
||||||
|
href=${"/group/"+group.id}
|
||||||
|
>
|
||||||
|
$img
|
||||||
|
<h4>${group.name}</h4>
|
||||||
|
<div className="address">${addressBlock}</div>
|
||||||
|
${distance}
|
||||||
|
</a>');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package react.order;
|
||||||
|
import react.ReactDOM;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
import utils.HttpUtil;
|
||||||
|
import react.product.ProductSelect;
|
||||||
|
import react.router.Redirect;
|
||||||
|
import react.router.Link;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A box to add an order to a member
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class InsertOrder extends react.ReactComponentOfPropsAndState<{contractId:Int,userId:Int,distributionId:Int,onInsert:UserOrder->Void},{products:Array<ProductInfo>,error:String,selected:Int}>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
state = {products:[],error:null,selected:null};
|
||||||
|
}
|
||||||
|
|
||||||
|
override function componentDidMount()
|
||||||
|
{
|
||||||
|
//load product list
|
||||||
|
HttpUtil.fetch("/api/product/get/", GET, {contractId:props.contractId},PLAIN_TEXT)
|
||||||
|
.then(function(data:String) {
|
||||||
|
|
||||||
|
/*var data : {products:Array<ProductInfo>} = haxe.Json.parse(data);
|
||||||
|
for( p in data.products) {
|
||||||
|
p.unitType = Type.createEnumIndex(UnitType,untyped p.unitType);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
var data : {products:Array<ProductInfo>} = tink.Json.parse(data);
|
||||||
|
setState({products:data.products, error:null,selected:null});
|
||||||
|
|
||||||
|
}).catchError(function(data) {
|
||||||
|
var data = Std.string(data);
|
||||||
|
trace("Error",data);
|
||||||
|
if(data.substr(0,1)=="{"){
|
||||||
|
//json error from server
|
||||||
|
var data : ErrorInfos = haxe.Json.parse(data);
|
||||||
|
setState(cast {error:data.error.message} );
|
||||||
|
}else{
|
||||||
|
//js error
|
||||||
|
setState(cast {error:data} );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
//redirect to orderBox if a product is selected
|
||||||
|
var redirect = if(state.selected!=null) jsx('<$Redirect to="/" />') else null;
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div>
|
||||||
|
$redirect
|
||||||
|
<h3>Choisissez le produit à ajouter</h3>
|
||||||
|
<$Link className="btn btn-default" to="/"><span className="glyphicon glyphicon-chevron-left"></span> Retour</$Link>
|
||||||
|
<$Error error="${state.error}" />
|
||||||
|
<hr />
|
||||||
|
<$ProductSelect onSelect=$onSelectProduct products=${state.products} />
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSelectProduct(p:ProductInfo){
|
||||||
|
var uo : UserOrder = cast {
|
||||||
|
id:null,
|
||||||
|
product:p,
|
||||||
|
quantity:1,
|
||||||
|
productId:p.id,
|
||||||
|
productPrice:p.price,
|
||||||
|
paid:false,
|
||||||
|
invert:false,
|
||||||
|
user2:null
|
||||||
|
};
|
||||||
|
props.onInsert(uo);
|
||||||
|
setState(cast {selected:p.id});
|
||||||
|
|
||||||
|
//do not insert order now, just warn the OrderBox
|
||||||
|
/*
|
||||||
|
//insert order
|
||||||
|
var data = [{id:null,productId:p.id,qt:1,paid:false,invert:false,user2:null} ];
|
||||||
|
var req = {
|
||||||
|
orders:haxe.Json.stringify(data),
|
||||||
|
distributionId : props.distributionId,
|
||||||
|
contractId : props.contractId
|
||||||
|
};
|
||||||
|
var r = HttpUtil.fetch("/api/order/update/"+props.userId, POST, req, JSON);
|
||||||
|
r.then(function(d:Dynamic) {
|
||||||
|
|
||||||
|
if (Reflect.hasField(d, "error")) {
|
||||||
|
setState(cast {error:d.error.message});
|
||||||
|
}else{
|
||||||
|
//WOOT
|
||||||
|
//trace("OK");
|
||||||
|
//go to OrderBox with a redirect
|
||||||
|
setState(cast {selected:p.id});
|
||||||
|
}
|
||||||
|
}).catchError(function(d) {
|
||||||
|
trace("PROMISE ERROR", d);
|
||||||
|
setState(cast {error:d.error.message});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package react.order;
|
||||||
|
import react.ReactDOM;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
import react.product.Product;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A User order
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class Order extends react.ReactComponentOfPropsAndState<{order:UserOrder,onUpdate:UserOrder->Void,parentBox:react.order.OrderBox},{order:UserOrder,inputValue:String}>
|
||||||
|
{
|
||||||
|
var hasPayments :Bool;
|
||||||
|
var currency : String;
|
||||||
|
|
||||||
|
public function new(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
state = {order:props.order,inputValue:null};
|
||||||
|
hasPayments = props.parentBox.props.hasPayments;
|
||||||
|
currency = props.parentBox.props.currency;
|
||||||
|
|
||||||
|
if (state.order.productUnit == null) state.order.productUnit = Piece;
|
||||||
|
if (state.order.productQt == null) state.order.productQt = 1;
|
||||||
|
|
||||||
|
state.inputValue = if ( isSmartQtInput(state.order) ){
|
||||||
|
Std.string(round(state.order.quantity * state.order.productQt));
|
||||||
|
}else{
|
||||||
|
Std.string(state.order.quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
var o = state.order;
|
||||||
|
/*var unit = if (o.productHasFloatQt || o.productHasVariablePrice){
|
||||||
|
jsx('<div className="col-md-1">${Formatting.unit(o.productUnit)}</div>');
|
||||||
|
}else{
|
||||||
|
jsx('<div className="col-md-1"></div>');
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
//use smart qt only if hasFloatQt
|
||||||
|
var productName = if (o.productHasFloatQt || o.productHasVariablePrice){
|
||||||
|
jsx('<div className="col-md-3">${o.productName}</div>');
|
||||||
|
}else{
|
||||||
|
jsx('<div className="col-md-3">${o.productName} ${o.productQt} ${Formatting.unit(o.productUnit)}</div>');
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/*var productName = if (o.productHasFloatQt || o.productHasVariablePrice){
|
||||||
|
jsx('<div className="col-md-3">${o.productName}</div>');*/
|
||||||
|
|
||||||
|
var input = if (isSmartQtInput(o)){
|
||||||
|
jsx('<div className="input-group">
|
||||||
|
<input type="text" className="form-control input-sm text-right" value="${state.inputValue}" onChange=${onChange} onKeyPress=${onKeyPress}/>
|
||||||
|
<div className="input-group-addon">${Formatting.unit(o.productUnit)}</div>
|
||||||
|
</div>');
|
||||||
|
}else{
|
||||||
|
jsx('<div className="input-group">
|
||||||
|
<input type="text" className="form-control input-sm text-right" value="${state.inputValue}" onChange=${onChange} onKeyPress=${onKeyPress}/>
|
||||||
|
</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
var alternated = if(props.parentBox.props.contractType==0 && props.parentBox.state.users!=null){
|
||||||
|
//constant orders
|
||||||
|
var options = props.parentBox.state.users.map(function(x) return jsx('<option key=${x.id} value=${x.id}>${x.name}</option>') );
|
||||||
|
|
||||||
|
var checkbox = if(o.invertSharedOrder){
|
||||||
|
jsx('<input data-toggle="tooltip" title="Inverser l\'alternance" checked="checked" type="checkbox" value="1" onChange=$onChangeInvert />');
|
||||||
|
}else{
|
||||||
|
jsx('<input data-toggle="tooltip" title="Inverser l\'alternance" type="checkbox" value="1" onChange=$onChangeInvert />');
|
||||||
|
}
|
||||||
|
|
||||||
|
jsx('<div>
|
||||||
|
<select className="form-control input-sm" style=${{width:"150px",display:"inline-block"}} onChange=${onChangeUser2} value=${o.userId2}>
|
||||||
|
<option value="0">-</option>
|
||||||
|
$options
|
||||||
|
</select>
|
||||||
|
$checkbox
|
||||||
|
</div>');
|
||||||
|
}else{
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsx('<div className="productOrder row">
|
||||||
|
<div className="col-md-4">
|
||||||
|
<$Product productInfo=${o.product} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-1 ref">
|
||||||
|
${o.productRef}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-1">
|
||||||
|
${round(o.quantity * o.productPrice)} ${currency}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-2">
|
||||||
|
$input
|
||||||
|
${makeInfos()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${paidInput()}
|
||||||
|
|
||||||
|
<div className="col-md-3">$alternated</div>
|
||||||
|
|
||||||
|
</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function round(f){
|
||||||
|
return Formatting.formatNum(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
function paidInput(){
|
||||||
|
if(hasPayments) return null;
|
||||||
|
if(state.order.paid){
|
||||||
|
return jsx('<div className="col-md-1"><input type="checkbox" name="paid" value="1" checked="checked" onChange=${onChangePaid} /></div>');
|
||||||
|
}else{
|
||||||
|
return jsx('<div className="col-md-1"><input type="checkbox" name="paid" value="1" onChange=${onChangePaid} /></div>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeInfos(){
|
||||||
|
var o = state.order;
|
||||||
|
return if ( isSmartQtInput(o) ){
|
||||||
|
jsx('<div className="infos">
|
||||||
|
<b> ${round(o.quantity)} </b> x <b>${o.productQt} ${Formatting.unit(o.productUnit)}</b > ${o.productName}
|
||||||
|
</div>');
|
||||||
|
}else{
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSmartQtInput(o:UserOrder):Bool{
|
||||||
|
return o.product.hasFloatQt || o.product.variablePrice || o.product.wholesale;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(e:js.html.Event){
|
||||||
|
e.preventDefault();
|
||||||
|
var value :String = untyped (e.target.value == "") ? "0" : e.target.value;
|
||||||
|
state.inputValue = value;
|
||||||
|
var v = Formatting.parseFloat(value);
|
||||||
|
var o = state.order;
|
||||||
|
if ( isSmartQtInput(o) ){
|
||||||
|
//the value is a smart qt, so we need re-compute the quantity
|
||||||
|
o.quantity = v / o.productQt;
|
||||||
|
}else{
|
||||||
|
o.quantity = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(state);
|
||||||
|
if (props.onUpdate != null) props.onUpdate(state.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangePaid(e:js.html.Event){
|
||||||
|
state.order.paid = untyped e.target.checked;
|
||||||
|
this.setState(state);
|
||||||
|
if (props.onUpdate != null) props.onUpdate(state.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeInvert(e:js.html.Event){
|
||||||
|
state.order.invertSharedOrder = untyped e.target.checked;
|
||||||
|
this.setState(state);
|
||||||
|
if (props.onUpdate != null) props.onUpdate(state.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeUser2(e:js.html.Event){
|
||||||
|
var v = Std.parseInt(untyped e.target.value);
|
||||||
|
state.order.userId2 = v==0 ? null : v;
|
||||||
|
this.setState(state);
|
||||||
|
if (props.onUpdate != null) props.onUpdate(state.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyPress(event:js.html.KeyboardEvent){
|
||||||
|
/*if(event.key == 'Enter'){
|
||||||
|
trace('enter !');
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package react.order;
|
||||||
|
import react.ReactDOM;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
import utils.HttpUtil;
|
||||||
|
import react.router.HashRouter;
|
||||||
|
import react.router.Route;
|
||||||
|
import react.router.Switch;
|
||||||
|
import react.router.Link;
|
||||||
|
|
||||||
|
typedef OrderBoxState = {
|
||||||
|
orders:Array<UserOrder>,
|
||||||
|
error:String,
|
||||||
|
users:Null<Array<UserInfo>>,
|
||||||
|
};
|
||||||
|
typedef OrderBoxProps = {
|
||||||
|
userId:Int,
|
||||||
|
distributionId:Int,
|
||||||
|
contractId:Int,
|
||||||
|
contractType:Int,
|
||||||
|
date:String,
|
||||||
|
place:String,
|
||||||
|
userName:String,
|
||||||
|
onValidate:Void->Void,
|
||||||
|
currency:String,
|
||||||
|
hasPayments:Bool
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A box to edit/add orders of a member
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class OrderBox extends react.ReactComponentOfPropsAndState<OrderBoxProps,OrderBoxState>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
state = { orders : [], error : null, users:null };
|
||||||
|
}
|
||||||
|
|
||||||
|
override function componentDidMount()
|
||||||
|
{
|
||||||
|
|
||||||
|
//request api avec user + distrib
|
||||||
|
HttpUtil.fetch("/api/order/get/"+props.userId, GET, {distributionId:props.distributionId,contractId:props.contractId}, PLAIN_TEXT)
|
||||||
|
.then(function(data:String) {
|
||||||
|
|
||||||
|
var data : {orders:Array<UserOrder>} = tink.Json.parse(data);
|
||||||
|
/*for( o in orders){
|
||||||
|
//convert ints to enums, enums have been lost in json serialization
|
||||||
|
o.productUnit = Type.createEnumIndex(Unit, cast o.productUnit );
|
||||||
|
}*/
|
||||||
|
setState({orders:data.orders, error:null});
|
||||||
|
|
||||||
|
if(props.contractType==0) loadUsers();
|
||||||
|
|
||||||
|
}).catchError(function(data) {
|
||||||
|
var data = Std.string(data);
|
||||||
|
trace("Error",data);
|
||||||
|
if(data.substr(0,1)=="{"){
|
||||||
|
//json error from server
|
||||||
|
var data : ErrorInfos = haxe.Json.parse(data);
|
||||||
|
setState( cast {error:data.error.message} );
|
||||||
|
}else{
|
||||||
|
//js error
|
||||||
|
setState( cast {error:data} );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load user list when contract is constant orders
|
||||||
|
*/
|
||||||
|
function loadUsers(){
|
||||||
|
HttpUtil.fetch("/api/user/getFromGroup/", GET, {}, PLAIN_TEXT)
|
||||||
|
.then(function(data:String) {
|
||||||
|
|
||||||
|
var data : {users:Array<UserInfo>} = tink.Json.parse(data);
|
||||||
|
setState({users:data.users, error:null});
|
||||||
|
|
||||||
|
}).catchError(function(data) {
|
||||||
|
|
||||||
|
var data = Std.string(data);
|
||||||
|
if(data.substr(0,1)=="{"){
|
||||||
|
//json error from server
|
||||||
|
var data : ErrorInfos = haxe.Json.parse(data);
|
||||||
|
setState( cast {error:data.error.message} );
|
||||||
|
}else{
|
||||||
|
//js error
|
||||||
|
setState( cast {error:data} );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
|
||||||
|
//edit orders
|
||||||
|
|
||||||
|
|
||||||
|
var renderOrders = this.state.orders.map(function(o){
|
||||||
|
var k :String = if(o.id!=null) {
|
||||||
|
Std.string(o.id);
|
||||||
|
} else {
|
||||||
|
o.productId+"-"+Std.random(99999);
|
||||||
|
};
|
||||||
|
return jsx('<$Order key="$k" order="$o" onUpdate=$onUpdate parentBox=${this} />') ;
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
var delivery = if(props.date==null){
|
||||||
|
null;
|
||||||
|
}else{
|
||||||
|
jsx('<p>Pour la livraison du <b>${props.date}</b> à <b>${props.place}</b></p>');
|
||||||
|
}
|
||||||
|
|
||||||
|
var renderOrderBox = function() return jsx('
|
||||||
|
<div onKeyPress=${onKeyPress}>
|
||||||
|
<h3>Commandes de ${props.userName}</h3>
|
||||||
|
$delivery
|
||||||
|
<$Error error="${state.error}" />
|
||||||
|
<hr/>
|
||||||
|
<div className="row tableHeader">
|
||||||
|
<div className="col-md-4">Produit</div>
|
||||||
|
<div className="col-md-1">Ref.</div>
|
||||||
|
<div className="col-md-1">Prix</div>
|
||||||
|
<div className="col-md-2">Qté</div>
|
||||||
|
<div className="col-md-1">Payé</div>
|
||||||
|
<div className="col-md-3">Alterné avec</div>
|
||||||
|
</div>
|
||||||
|
${renderOrders}
|
||||||
|
<div>
|
||||||
|
<a onClick=${onClick} className="btn btn-primary">
|
||||||
|
<span className="glyphicon glyphicon-chevron-right"></span> Valider
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<$Link className="btn btn-default" to="/insert"><span className="glyphicon glyphicon-plus-sign"></span> Ajouter un produit</$Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
|
||||||
|
|
||||||
|
var onProductSelected = function(uo:UserOrder){
|
||||||
|
|
||||||
|
var existingOrder = Lambda.find(state.orders,function(x) return x.productId==uo.productId );
|
||||||
|
if(existingOrder!=null){
|
||||||
|
existingOrder.quantity += uo.quantity;
|
||||||
|
this.setState(this.state);
|
||||||
|
}else{
|
||||||
|
this.state.orders.push(uo);
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//insert product box
|
||||||
|
var renderInsertBox = function(){
|
||||||
|
return jsx('<$InsertOrder contractId="${props.contractId}" userId="${props.userId}" distributionId="${props.distributionId}" onInsert=$onProductSelected/>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsx('<$HashRouter>
|
||||||
|
<$Switch>
|
||||||
|
<$Route path="/" exact=$true render=$renderOrderBox />
|
||||||
|
<$Route path="/insert" exact=$true render=$renderInsertBox />
|
||||||
|
</$Switch>
|
||||||
|
</$HashRouter>');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called when an order is updated
|
||||||
|
*/
|
||||||
|
function onUpdate(data:UserOrder){
|
||||||
|
/*trace("ON UPDATE : " + data);
|
||||||
|
for ( o in state.orders){
|
||||||
|
if (o.id == data.id) {
|
||||||
|
o.quantity = data.quantity;
|
||||||
|
o.paid = data.paid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(this.state);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* submit updated orders to the API
|
||||||
|
*/
|
||||||
|
function onClick(?_){
|
||||||
|
|
||||||
|
var data = new Array<{id:Int,productId:Int,qt:Float,paid:Bool,invertSharedOrder:Bool,userId2:Int}>();
|
||||||
|
for ( o in state.orders) data.push({id:o.id, productId : o.productId, qt: o.quantity, paid : o.paid, invertSharedOrder:o.invertSharedOrder, userId2:o.userId2});
|
||||||
|
|
||||||
|
var req = { orders:data };
|
||||||
|
|
||||||
|
var p = HttpUtil.fetch("/api/order/update/"+props.userId+"?distributionId="+props.distributionId+"&contractId="+props.contractId, POST, req, JSON);
|
||||||
|
p.then(function(data:Dynamic) {
|
||||||
|
|
||||||
|
//WOOT
|
||||||
|
if (props.onValidate != null) props.onValidate();
|
||||||
|
|
||||||
|
}).catchError(function(data) {
|
||||||
|
var data = Std.string(data);
|
||||||
|
trace("Error",data);
|
||||||
|
if(data.substr(0,1)=="{"){
|
||||||
|
//json error from server
|
||||||
|
var data : ErrorInfos = haxe.Json.parse(data);
|
||||||
|
setState( cast {error:data.error.message} );
|
||||||
|
}else{
|
||||||
|
//js error
|
||||||
|
setState( cast {error:data} );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyPress(e:js.html.KeyboardEvent){
|
||||||
|
if(e.key=="Enter") onClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package react;
|
||||||
|
|
||||||
|
import api.react.ReactMacro.jsx;
|
||||||
|
import js.html.InputElement;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
typedef ComposerAppState = {
|
||||||
|
products:Array<{id:Int,name:String,qt:Float,unit:UnitType}>
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef ComposerAppRefs = {
|
||||||
|
pi:ProductInput,
|
||||||
|
productContainer:js.html.DivElement,
|
||||||
|
qt:InputElement,
|
||||||
|
unit:js.html.SelectElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composite product composer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ComposerApp extends ReactComponentOfStateAndRefs<ComposerAppState, ComposerAppRefs>
|
||||||
|
{
|
||||||
|
/*var products:Array<{id:Int,name:String,?qt:Float,?unit:UnitType}>;
|
||||||
|
|
||||||
|
|
||||||
|
public function new(props:Dynamic)
|
||||||
|
{
|
||||||
|
|
||||||
|
super(props);
|
||||||
|
products = [{id:1,name:"pipo"},{id:2,name:"Loclac"}];
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
return jsx('
|
||||||
|
<div className="ComposerApp" style={{margin:"10px"}} >
|
||||||
|
|
||||||
|
<div className="form-inline">
|
||||||
|
|
||||||
|
<ProductInput ref="pi"/>
|
||||||
|
|
||||||
|
<input ref="qt" onChange="$onChange" className="form-control" type="text" name="qt" placeholder="Quantité" />
|
||||||
|
|
||||||
|
<select ref="unit" className="form-control" name="unit">
|
||||||
|
${getUnits()}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<a className="btn btn-primary" onClick=$addItem>
|
||||||
|
<span className="glyphicon glyphicon-plus"></span> Ajouter
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container" ref="productContainer">
|
||||||
|
${createChildren()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUnits(){
|
||||||
|
var out = [];
|
||||||
|
for ( c in Unit.createAll()){
|
||||||
|
|
||||||
|
out.push(jsx( '<option value="{c.getIndex()}"> {Std.string(c)} </option>'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createChildren()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
return [for (p in products) jsx('<ProductComp key={p.id} name={p.name} qt={p.qt} unit={p.unit}/>')];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addItem(){
|
||||||
|
var text :String = refs.pi.refs.input.value;
|
||||||
|
if (text.length > 0)
|
||||||
|
{
|
||||||
|
trace("add " + text);
|
||||||
|
trace("qt " + this.refs.qt.value);
|
||||||
|
trace("unit " + this.refs.unit.selectedIndex);
|
||||||
|
|
||||||
|
var qt = Std.parseFloat(this.refs.qt.value);
|
||||||
|
var unit = UnitType.createByIndex(this.refs.unit.selectedIndex);
|
||||||
|
var id = Std.random(999);
|
||||||
|
products.push( {id:id, name:text,qt:qt,unit:unit});
|
||||||
|
|
||||||
|
setState({products:[{id:id,name:text, qt:qt, unit:unit}]});
|
||||||
|
//this.forceUpdate();
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package react.product;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Product
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class Product extends react.ReactComponentOfProps<{productInfo:ProductInfo}>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
var p :ProductInfo = props.productInfo;
|
||||||
|
|
||||||
|
//convert int to enum
|
||||||
|
//p.unitType = Type.createEnumIndex(Common.Unit,cast p.unit);
|
||||||
|
|
||||||
|
//var unit = ;
|
||||||
|
var imgStyle = {width:'64px',height:'64px','backgroundImage':'url("${p.image}")'};
|
||||||
|
var divStyle = p.active ? {} : {opacity: 0.4};
|
||||||
|
|
||||||
|
return jsx('<div className="product row" style=$divStyle>
|
||||||
|
<div className="col-md-4">
|
||||||
|
<div src="${p.image}" className="productImg" style=$imgStyle/>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-8">
|
||||||
|
<strong>${p.name}</strong> ${p.qt} ${Formatting.unit(p.unitType)}<br/>
|
||||||
|
${p.price} €
|
||||||
|
</div>
|
||||||
|
</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package react.product;
|
||||||
|
import react.ReactDOM;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
import react.Typeahead;
|
||||||
|
|
||||||
|
typedef ProductInputProps = {
|
||||||
|
formName:String,
|
||||||
|
txpProductId:Int,
|
||||||
|
productName:String,
|
||||||
|
}
|
||||||
|
typedef ProductInputState = {
|
||||||
|
txpProductId:Int,
|
||||||
|
productName:String,
|
||||||
|
categoryId:Int,
|
||||||
|
breadcrumb:String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product Text Input with autocompletion
|
||||||
|
*
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class ProductInput extends react.ReactComponentOfPropsAndState<ProductInputProps,ProductInputState>
|
||||||
|
{
|
||||||
|
|
||||||
|
public static var DICO : TxpDictionnary = null;
|
||||||
|
var options : Array<{id:Int,label:String}>;
|
||||||
|
|
||||||
|
public function new(props:ProductInputProps)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
options = [];
|
||||||
|
this.state = {
|
||||||
|
txpProductId : props.txpProductId,
|
||||||
|
productName : props.productName,
|
||||||
|
categoryId : 0,
|
||||||
|
breadcrumb : ""
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
var inputName :String = props.formName+"_name";
|
||||||
|
var txpProductInputName :String = props.formName+"_txpProductId";
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="row">
|
||||||
|
|
||||||
|
<div className="col-md-8">
|
||||||
|
<AsyncTypeahead
|
||||||
|
placeholder="Saisissez un nom de produit"
|
||||||
|
options=$options
|
||||||
|
onSearch=$onSearch
|
||||||
|
minLength={3}
|
||||||
|
style={{width:"350px"}}
|
||||||
|
onChange=$onChange
|
||||||
|
onInputChange=$onInputChange
|
||||||
|
selected={["${state.productName}"]}
|
||||||
|
isLoading=$true
|
||||||
|
/>
|
||||||
|
<div className = "txpProduct" > ${state.breadcrumb}</div>
|
||||||
|
|
||||||
|
<input className="txpProduct" type="hidden" name="$txpProductInputName" value="${state.txpProductId}" />
|
||||||
|
<input className="txpProduct" type="hidden" name="$inputName" value="${state.productName}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-4">
|
||||||
|
<img ref="image" className="img-thumbnail" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when typing is stopped
|
||||||
|
* @param o
|
||||||
|
*/
|
||||||
|
function onSearch(o){
|
||||||
|
//trace("on search : "+o);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each time a single letter change in the input
|
||||||
|
* @param input
|
||||||
|
*/
|
||||||
|
function onInputChange(input:String){
|
||||||
|
trace('on input change $input');
|
||||||
|
this.setState({productName:input});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an item is selected in suggestions
|
||||||
|
*/
|
||||||
|
function onChange(selection:Array<{label:String,id:Int}>){
|
||||||
|
|
||||||
|
if (selection == null || selection.length == 0) return;
|
||||||
|
|
||||||
|
trace("on change "+selection[0]);
|
||||||
|
|
||||||
|
var product = Lambda.find(DICO.products, function(x) return x.id == selection[0].id);
|
||||||
|
setTaxo(product);
|
||||||
|
this.setState({productName:selection[0].label});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init typeahead auto-completion features when component is mounted
|
||||||
|
*/
|
||||||
|
override function componentDidMount(){
|
||||||
|
|
||||||
|
//get dictionnary
|
||||||
|
if (DICO == null){
|
||||||
|
|
||||||
|
var r = new haxe.Http("/product/getTaxo");
|
||||||
|
r.onData = function(data){
|
||||||
|
//load dico
|
||||||
|
DICO = haxe.Unserializer.run(data);
|
||||||
|
|
||||||
|
for ( p in DICO.products){
|
||||||
|
options.push({label:p.name,id:p.id});
|
||||||
|
}
|
||||||
|
|
||||||
|
//default values of input
|
||||||
|
if (props.txpProductId != null){
|
||||||
|
var txp = Lambda.find(DICO.products, function(x) return x.id == props.txpProductId);
|
||||||
|
setTaxo(txp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.request();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTaxo(txp:{id:Int, name:String, category:Int, subCategory:Int}){
|
||||||
|
|
||||||
|
if (txp == null) return;
|
||||||
|
|
||||||
|
//trace(txp);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
categoryId:txp.category,
|
||||||
|
txpProductId:txp.id,
|
||||||
|
breadcrumb:getBreadcrumb(txp)/*,
|
||||||
|
productName:product.name //do not override product name ! */
|
||||||
|
});
|
||||||
|
|
||||||
|
this.refs.image.src="/img/taxo/cat"+txp.category+".png";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate string like "fruits & vegetables / vegetables / carrots"
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
function getBreadcrumb(product){
|
||||||
|
//cat
|
||||||
|
var str = DICO.categories.get(product.category).name;
|
||||||
|
if (product.subCategory != null){
|
||||||
|
str += " / " + DICO.subCategories.get(product.subCategory).name;
|
||||||
|
}
|
||||||
|
str += " / " + product.name;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package react.product;
|
||||||
|
import react.ReactDOM;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
import utils.HttpUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Product selector
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class ProductSelect extends react.ReactComponentOfPropsAndState<{onSelect:ProductInfo->Void,products:Array<ProductInfo>},{selected:Int}>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
state = { selected : null };
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
|
||||||
|
var products = props.products.map(function(info){
|
||||||
|
//var selector = info.id==state.selected ? jsx(''):jsx('<div className="clickable"><$Product productInfo=$info /></div>');
|
||||||
|
return jsx('<div key=${info.id} className="col-md-6" onClick=${onClick.bind(info.id)}>
|
||||||
|
<div className="clickable"><$Product productInfo=$info /></div>
|
||||||
|
</div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsx('<div className="productSelect">${products}</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClick(i:Int){
|
||||||
|
this.setState(cast {selected:i});
|
||||||
|
if(props.onSelect!=null){
|
||||||
|
var p = Lambda.find(props.products,function(x) return x.id==i);
|
||||||
|
props.onSelect(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package react.store;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
typedef CartProps = {
|
||||||
|
var order:OrderSimple;
|
||||||
|
var addToCart:ProductInfo -> Int -> Void;
|
||||||
|
var removeFromCart:ProductInfo -> ?Int -> Void;
|
||||||
|
var submitOrder:OrderSimple -> Void;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Cart extends react.ReactComponentOfProps<CartProps>
|
||||||
|
{
|
||||||
|
|
||||||
|
function addToCart(product:ProductInfo, quantity:Int):Void {
|
||||||
|
props.addToCart(product, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromCart(product:ProductInfo, quantity:Int):Void {
|
||||||
|
props.removeFromCart(product, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAllFromCart(product:ProductInfo):Void {
|
||||||
|
props.removeFromCart(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitOrder():Void {
|
||||||
|
props.submitOrder(props.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
return jsx('
|
||||||
|
<div className="cart">
|
||||||
|
<h3>Ma Commande</h3>
|
||||||
|
${renderProducts()}
|
||||||
|
${renderFooter()}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProducts() {
|
||||||
|
var productsToOrder = props.order.products.map(function(product:ProductWithQuantity) {
|
||||||
|
var quantity = product.quantity;
|
||||||
|
var product = product.product;
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="product-to-order" key=${product.name}>
|
||||||
|
<div>${product.name}</div>
|
||||||
|
<div>$quantity</div>
|
||||||
|
<div className="cart-action-buttons">
|
||||||
|
<div onClick=${function(){
|
||||||
|
this.addToCart(product, 1);
|
||||||
|
}}>
|
||||||
|
+
|
||||||
|
</div>
|
||||||
|
<div onClick=${function(){
|
||||||
|
this.removeFromCart(product, 1);
|
||||||
|
}}>
|
||||||
|
-
|
||||||
|
</div>
|
||||||
|
<div onClick=${function(){
|
||||||
|
this.removeAllFromCart(product);
|
||||||
|
}}>
|
||||||
|
x
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="products-to-order">
|
||||||
|
${productsToOrder}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFooter() {
|
||||||
|
var buttonClasses = ["order-button"];
|
||||||
|
var submit = submitOrder;
|
||||||
|
|
||||||
|
if (props.order.products.length == 0) {
|
||||||
|
buttonClasses.push("order-button--disabled");
|
||||||
|
submit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="cart-footer">
|
||||||
|
<div className="total">
|
||||||
|
Total
|
||||||
|
<div>${props.order.total} €</div>
|
||||||
|
</div>
|
||||||
|
<div className=${buttonClasses.join(" ")} onClick=$submit>Commander</div>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package react.store;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
typedef FiltersProps = {
|
||||||
|
var categories:Array<CategoryInfo>;
|
||||||
|
var filters:Array<String>;
|
||||||
|
var toggleFilter:String -> Void;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Filters extends react.ReactComponentOfProps<FiltersProps>
|
||||||
|
{
|
||||||
|
override public function render(){
|
||||||
|
return jsx('
|
||||||
|
<div className="filters">
|
||||||
|
<h3>Filtres</h3>
|
||||||
|
${renderFilters()}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFilters() {
|
||||||
|
return props.categories.map(function(category) {
|
||||||
|
var classNames = ["filter"];
|
||||||
|
if (props.filters.has(category.name))
|
||||||
|
classNames.push("active");
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div
|
||||||
|
className=${classNames.join(" ")}
|
||||||
|
key=${category.id}
|
||||||
|
onClick=${function(){
|
||||||
|
props.toggleFilter(category.name);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${category.name}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package react.store;
|
||||||
|
|
||||||
|
import js.Browser.window;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
typedef ProductProps = {
|
||||||
|
var product:ProductInfo;
|
||||||
|
var addToCart:ProductInfo -> Int -> Void;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef ProductState = {
|
||||||
|
var quantity:Int;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Product extends react.ReactComponentOfPropsAndState<ProductProps, ProductState>
|
||||||
|
{
|
||||||
|
static inline var OVERLAY_URL = '/shop/productInfo';
|
||||||
|
static inline var IMAGE_WIDTH = 120;
|
||||||
|
static inline var IMAGE_HEIGHT = 120;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
super();
|
||||||
|
state = {
|
||||||
|
quantity: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openOverlay() {
|
||||||
|
untyped window._.overlay('$OVERLAY_URL/${props.product.id}', props.product.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateQuantity(event:Dynamic) {
|
||||||
|
var quantity = Std.parseInt(event.target.value);
|
||||||
|
|
||||||
|
if (Std.is(quantity, Int) && quantity > 0)
|
||||||
|
setState({
|
||||||
|
quantity: Std.int(quantity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToCart() {
|
||||||
|
props.addToCart(props.product, state.quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
var product = props.product;
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="product">
|
||||||
|
<img src=${product.image} width=${IMAGE_WIDTH+'px'} height=${IMAGE_HEIGHT+'px'} alt={product.name} />
|
||||||
|
<div className="body">
|
||||||
|
<a onClick=$openOverlay>
|
||||||
|
${product.name}
|
||||||
|
</a>
|
||||||
|
<div>${product.price} €</div>
|
||||||
|
<input type="number" value=${state.quantity} onChange=$updateQuantity />
|
||||||
|
<div className="button" onClick=$addToCart>Ajouter</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package react.store;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
typedef ProductListProps = {
|
||||||
|
var categories:Array<CategoryInfo>;
|
||||||
|
var productsBySubcategoryIdMap:Map<Int, Array<ProductInfo>>;
|
||||||
|
var filters:Array<String>;
|
||||||
|
var addToCart:ProductInfo -> Int -> Void;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProductList extends react.ReactComponentOfProps<ProductListProps>
|
||||||
|
{
|
||||||
|
override public function render(){
|
||||||
|
return jsx('
|
||||||
|
<div className="categories">
|
||||||
|
${renderCategories()}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCategories() {
|
||||||
|
return props.categories.map(function(category) {
|
||||||
|
if (!props.filters.has(category.name))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="category" key=${category.name}>
|
||||||
|
<h2>${category.name}</h2>
|
||||||
|
<div className="subCategories">
|
||||||
|
${renderSubCategories(category)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSubCategories(category) {
|
||||||
|
var subCategories = category.subcategories.map(function(category) {
|
||||||
|
if (!props.productsBySubcategoryIdMap.exists(category.id))
|
||||||
|
return jsx('<div key=${category.name}>Loading...</div>');
|
||||||
|
|
||||||
|
var products = props.productsBySubcategoryIdMap.get(category.id);
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="sub-category" key=${category.name}>
|
||||||
|
<h3>${category.name}</h3>
|
||||||
|
<div className="products">
|
||||||
|
${renderProducts(products)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="sub-categories">
|
||||||
|
$subCategories
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProducts(products) {
|
||||||
|
return products.map(function(product) {
|
||||||
|
return jsx('
|
||||||
|
<Product product=${product} key=${product.id} addToCart=${props.addToCart}/>
|
||||||
|
');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package react.store;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import haxe.Json;
|
||||||
|
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
import Common;
|
||||||
|
import utils.CartUtils;
|
||||||
|
import utils.HttpUtil;
|
||||||
|
|
||||||
|
typedef StoreProps = {
|
||||||
|
var place:Int;
|
||||||
|
var date:String;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef StoreState = {
|
||||||
|
var place:PlaceInfos;
|
||||||
|
var orderByEndDates:Array<OrderByEndDate>;
|
||||||
|
var categories:Array<CategoryInfo>;
|
||||||
|
var productsBySubcategoryIdMap:Map<Int, Array<ProductInfo>>;
|
||||||
|
var order:OrderSimple;
|
||||||
|
var filters:Array<String>;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Store extends react.ReactComponentOfPropsAndState<StoreProps, StoreState>
|
||||||
|
{
|
||||||
|
static inline var CATEGORY_URL = '/api/shop/categories';
|
||||||
|
static inline var PRODUCT_URL = '/api/shop/products';
|
||||||
|
static inline var INIT_URL = '/api/shop/init';
|
||||||
|
static inline var VIEW_URL = '/place/view';
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
super();
|
||||||
|
state = {
|
||||||
|
place: null,
|
||||||
|
orderByEndDates: [],
|
||||||
|
categories: [],
|
||||||
|
filters: [],
|
||||||
|
productsBySubcategoryIdMap: new Map(),
|
||||||
|
order: {
|
||||||
|
products: [],
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
override function componentDidMount() {
|
||||||
|
var categoriesRequest = HttpUtil.fetch(CATEGORY_URL, GET, {date: props.date, place: props.place}, JSON);
|
||||||
|
var initRequest = HttpUtil.fetch(INIT_URL, GET, {date: props.date, place: props.place}, JSON);
|
||||||
|
|
||||||
|
initRequest.then(function(infos:Dynamic) {
|
||||||
|
setState({
|
||||||
|
place: infos.place,
|
||||||
|
orderByEndDates: infos.orderEndDates
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catchError(function(error) {
|
||||||
|
trace("ERROR", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
categoriesRequest.then(function(categories:Dynamic) {
|
||||||
|
var categories:Array<CategoryInfo> = categories.categories;
|
||||||
|
var subCategories = [];
|
||||||
|
|
||||||
|
for (category in categories) {
|
||||||
|
subCategories = subCategories.concat(category.subcategories);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState({
|
||||||
|
categories: categories,
|
||||||
|
filters: categories.map(function(category) {
|
||||||
|
return category.name;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
subCategories.map(function(category:CategoryInfo) {
|
||||||
|
return HttpUtil.fetch(PRODUCT_URL, GET, {date: props.date, place: props.place, subcategory: category.id}, JSON)
|
||||||
|
.then(function(result) {
|
||||||
|
var productsBySubcategoryIdMapCopy = [
|
||||||
|
for (key in state.productsBySubcategoryIdMap.keys())
|
||||||
|
key => state.productsBySubcategoryIdMap.get(key)
|
||||||
|
];
|
||||||
|
productsBySubcategoryIdMapCopy.set(category.id, result.products);
|
||||||
|
|
||||||
|
setState({
|
||||||
|
productsBySubcategoryIdMap: productsBySubcategoryIdMapCopy
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catchError(function(error) {
|
||||||
|
trace("ERROR", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFilter(category:String) {
|
||||||
|
var filters = state.filters.copy();
|
||||||
|
|
||||||
|
if (state.filters.find(function(categoryInFilter) {
|
||||||
|
return category == categoryInFilter;
|
||||||
|
}) != null)
|
||||||
|
filters.remove(category);
|
||||||
|
else
|
||||||
|
filters.push(category);
|
||||||
|
|
||||||
|
if (filters.length == 0)
|
||||||
|
filters = state.categories.map(function(category) {
|
||||||
|
return category.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
setState({
|
||||||
|
filters: filters
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToCart(productToAdd:ProductInfo, quantity:Int):Void {
|
||||||
|
setState({
|
||||||
|
order: CartUtils.addToCart(state.order, productToAdd, quantity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromCart(productToRemove:ProductInfo, ?quantity:Int):Void {
|
||||||
|
setState({
|
||||||
|
order: CartUtils.removeFromCart(state.order, productToRemove, quantity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitOrder(order:OrderSimple) {
|
||||||
|
var orderInSession = {
|
||||||
|
total: order.total,
|
||||||
|
products: order.products.map(function(p:ProductWithQuantity){
|
||||||
|
return {
|
||||||
|
productId: p.product.id,
|
||||||
|
quantity: p.quantity
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
trace('Order', orderInSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
return jsx('
|
||||||
|
<div className="shop">
|
||||||
|
${renderHeader()}
|
||||||
|
<ProductList
|
||||||
|
categories=${state.categories}
|
||||||
|
productsBySubcategoryIdMap=${state.productsBySubcategoryIdMap}
|
||||||
|
filters=${state.filters}
|
||||||
|
addToCart=$addToCart
|
||||||
|
/>
|
||||||
|
<Filters
|
||||||
|
categories=${state.categories}
|
||||||
|
filters=${state.filters}
|
||||||
|
toggleFilter=$toggleFilter
|
||||||
|
/>
|
||||||
|
<Cart
|
||||||
|
order=${state.order}
|
||||||
|
addToCart=$addToCart
|
||||||
|
removeFromCart=$removeFromCart
|
||||||
|
submitOrder=$submitOrder
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHeader() {
|
||||||
|
if (state.orderByEndDates == null || state.orderByEndDates.length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var endDates;
|
||||||
|
|
||||||
|
if (state.orderByEndDates.length == 1) {
|
||||||
|
var orderEndDate = state.orderByEndDates[0].date;
|
||||||
|
endDates = [jsx('<div key=$orderEndDate>La commande fermera le $orderEndDate</div>')];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
endDates = state.orderByEndDates.map(function(order) {
|
||||||
|
if (order.contracts.length == 1)
|
||||||
|
return jsx('
|
||||||
|
<div key=${order.date}>
|
||||||
|
La commande ${order.contracts[0]} fermera le: ${order.date}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div key=${order.date}>
|
||||||
|
Les autres commandes fermeront: ${order.date}
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewUrl = '$VIEW_URL/${props.place}';
|
||||||
|
var addressBlock = Lambda.array([
|
||||||
|
state.place.address1,
|
||||||
|
state.place.address2,
|
||||||
|
[state.place.zipCode, state.place.city].join(" "),
|
||||||
|
].mapi(function(index, element) {
|
||||||
|
if (element == null)
|
||||||
|
return null;
|
||||||
|
return jsx('<div className="address" key=$index>$element</div>');
|
||||||
|
}));
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div className="shop-header">
|
||||||
|
<div>
|
||||||
|
<div className="shop-distribution">
|
||||||
|
Distribution le ${props.date}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="shop-order-ends">
|
||||||
|
$endDates
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="shop-place">
|
||||||
|
<span className="info">
|
||||||
|
<span className="glyphicon glyphicon-map-marker"></span>
|
||||||
|
<a href=$viewUrl>${state.place.name}</a>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
$addressBlock
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package react.user;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
typedef LoginBoxProps = {
|
||||||
|
redirectUrl:String,
|
||||||
|
message:String,
|
||||||
|
?phoneRequired:Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef LoginBoxState = {
|
||||||
|
email:String,
|
||||||
|
password:String,
|
||||||
|
error:String
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login Box
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class LoginBox extends react.ReactComponentOfPropsAndState<LoginBoxProps,LoginBoxState>
|
||||||
|
{
|
||||||
|
|
||||||
|
public function new(props:LoginBoxProps)
|
||||||
|
{
|
||||||
|
if (props.redirectUrl == null) props.redirectUrl = "/";
|
||||||
|
if (props.message == "") props.message = null;
|
||||||
|
super(props);
|
||||||
|
this.state = {email:"", password:"", error:null};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setError(err:String){
|
||||||
|
this.setState(cast {error:err});
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
|
||||||
|
return jsx('<div onKeyPress=$onKeyPress>
|
||||||
|
<$Error error="${state.error}" />
|
||||||
|
<$Message message="${props.message}" />
|
||||||
|
<form action="" method="post" className="form-horizontal">
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="email" className="col-sm-4 control-label">Email : </label>
|
||||||
|
<div className="col-sm-8">
|
||||||
|
<input id="email" className="form-control" type="text" name="email" value="${state.email}" required="1" onChange={onChange}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="password" className="col-sm-4 control-label">Mot de passe : </label>
|
||||||
|
<div className="col-sm-8">
|
||||||
|
<input id="password" type="password" name="password" value="${state.password}" className="form-control" required="1" onChange={onChange}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-center">
|
||||||
|
<a onClick={submit} className="btn btn-primary btn-lg" ><span className="glyphicon glyphicon-user"></span> S\'identifier</a>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<a href="/user/forgottenPassword">Mot de passe oublié ?</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<!--
|
||||||
|
<hr/>
|
||||||
|
<p className="text-center">
|
||||||
|
<b>C\'est votre première visite sur Cagette.net ?</b>
|
||||||
|
<a onClick={registerBox} className="btn btn-default"><span className="glyphicon glyphicon-chevron-right"></span> S\'inscrire</a>
|
||||||
|
</p>
|
||||||
|
-->
|
||||||
|
</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @doc https://facebook.github.io/react/docs/forms.html
|
||||||
|
*/
|
||||||
|
function onChange(e:js.html.Event){
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var name :String = untyped e.target.name;
|
||||||
|
var value :String = untyped /*(e.target.value == "") ? null :*/ e.target.value;
|
||||||
|
Reflect.setField(state, name, value);
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* displays a registerBox
|
||||||
|
*/
|
||||||
|
public function registerBox(){
|
||||||
|
|
||||||
|
var body = js.Browser.document.querySelector('#myModal .modal-body');
|
||||||
|
ReactDOM.unmountComponentAtNode( body );
|
||||||
|
|
||||||
|
js.Browser.document.querySelector("#myModal .modal-title").innerHTML = "Inscription";
|
||||||
|
ReactDOM.render(jsx('<$RegisterBox redirectUrl="${props.redirectUrl}" phoneRequired="${props.phoneRequired}"/>'), body );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit(?e:js.html.Event){
|
||||||
|
|
||||||
|
if (state.email == ""){
|
||||||
|
setError("Veuillez saisir votre email");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.password == ""){
|
||||||
|
setError("Veuillez saisir votre mot de passe");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//lock button
|
||||||
|
var el: js.html.Element = null;
|
||||||
|
if(e!=null){
|
||||||
|
el = cast e.target;
|
||||||
|
el.classList.add("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var req = new haxe.Http("/api/user/login");
|
||||||
|
req.addParameter("email", state.email);
|
||||||
|
req.addParameter("password", state.password);
|
||||||
|
req.addParameter("redirecturl", props.redirectUrl);
|
||||||
|
|
||||||
|
req.onData = req.onError = function(d){
|
||||||
|
|
||||||
|
var d = req.responseData;
|
||||||
|
|
||||||
|
if(e!=null) el.classList.remove("disabled");
|
||||||
|
|
||||||
|
var d = haxe.Json.parse(d);
|
||||||
|
if (Reflect.hasField(d, "error")) setError(d.error.message);
|
||||||
|
if (Reflect.hasField(d, "success")) js.Browser.window.location.href = props.redirectUrl;
|
||||||
|
}
|
||||||
|
req.request(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyPress(e:js.html.KeyboardEvent){
|
||||||
|
if(e.key=="Enter") submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package react.user;
|
||||||
|
import react.ReactDOM;
|
||||||
|
import react.ReactComponent;
|
||||||
|
import react.ReactMacro.jsx;
|
||||||
|
|
||||||
|
typedef RegisterBoxState = {firstName:String, lastName:String, email:String, password:String, error:String, phone:String};
|
||||||
|
typedef RegisterBoxProps = {redirectUrl:String,message:String,phoneRequired:Bool};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration box ( sign up )
|
||||||
|
* @author fbarbut
|
||||||
|
*/
|
||||||
|
class RegisterBox extends react.ReactComponentOfPropsAndState<RegisterBoxProps,RegisterBoxState>
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public function new(props:RegisterBoxProps)
|
||||||
|
{
|
||||||
|
if (props.redirectUrl == null) props.redirectUrl = "/";
|
||||||
|
super(props);
|
||||||
|
this.state = {firstName:"",lastName:"",email:"",password:"",error:null,phone:""};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override public function render(){
|
||||||
|
|
||||||
|
//tips for conditionnal rendering : https://github.com/massiveinteractive/haxe-react#gotchas
|
||||||
|
var phone = null;
|
||||||
|
if (props.phoneRequired){
|
||||||
|
phone = jsx('<div className="form-group">
|
||||||
|
<label htmlFor="phone" className="col-sm-4 control-label">Téléphone : </label>
|
||||||
|
<div className="col-sm-8">
|
||||||
|
<input id="phone" type="text" className="form-control" name="phone" value="${state.phone}" onChange={onChange} />
|
||||||
|
</div>
|
||||||
|
</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsx('
|
||||||
|
<div>
|
||||||
|
<$Error error="${state.error}" />
|
||||||
|
<$Message message="${props.message}" />
|
||||||
|
<form action="" method="post" className="form-horizontal">
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="firstName" className="col-sm-4 control-label">Prénom : </label>
|
||||||
|
<div className="col-sm-8">
|
||||||
|
<input id="firstName" type="text" name="firstName" value="${state.firstName}" className="form-control" onChange={onChange}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="lastName" className="col-sm-4 control-label">Nom : </label>
|
||||||
|
<div className="col-sm-8">
|
||||||
|
<input id="lastName" type="text" name="lastName" value="${state.lastName}" className="form-control" onChange={onChange}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="email" className="col-sm-4 control-label">Email : </label>
|
||||||
|
<div className="col-sm-8">
|
||||||
|
<input id="email" type="text" className="form-control" name="email" value="${state.email}" onChange={onChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${phone}
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="password" className="col-sm-4 control-label">Mot de passe : </label>
|
||||||
|
<div className="col-sm-8">
|
||||||
|
<input id="password" type="password" name="password" value="${state.password}" className="form-control" onChange={onChange}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-center">
|
||||||
|
<a onClick={submit} className="btn btn-primary btn-lg" ><span className="glyphicon glyphicon-chevron-right"></span> Inscription</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<hr/>
|
||||||
|
<p className="text-center">
|
||||||
|
<b>Déjà inscrit ? </b>
|
||||||
|
<a onClick={loginBox} className="btn btn-default"><span className="glyphicon glyphicon-user"></span> Connectez-vous ici</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @doc https://facebook.github.io/react/docs/forms.html
|
||||||
|
*/
|
||||||
|
function onChange(e:js.html.Event){
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
var name :String = untyped e.target.name;
|
||||||
|
var value :String = untyped e.target.value;
|
||||||
|
//trace('onChange : $name = $value');
|
||||||
|
Reflect.setField(state, name, value);
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* displays a login box
|
||||||
|
*/
|
||||||
|
public function loginBox(){
|
||||||
|
|
||||||
|
var body = js.Browser.document.querySelector('#myModal .modal-body');
|
||||||
|
ReactDOM.unmountComponentAtNode( body );
|
||||||
|
|
||||||
|
js.Browser.document.querySelector("#myModal .modal-title").innerHTML = "Connexion";
|
||||||
|
ReactDOM.render(jsx('<$LoginBox redirectUrl="${props.redirectUrl}" />'), body );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function submit(e:js.html.Event ){
|
||||||
|
|
||||||
|
if (state.email == ""){
|
||||||
|
setError("Veuillez saisir votre email");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.password == ""){
|
||||||
|
setError("Veuillez saisir un mot de passe");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.firstName == ""){
|
||||||
|
setError("Veuillez saisir votre prénom");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.lastName == ""){
|
||||||
|
setError("Veuillez saisir votre nom de famille");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.phone == "" && props.phoneRequired){
|
||||||
|
setError("Veuillez saisir votre numéro de téléphone");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//lock button
|
||||||
|
var el : js.html.Element = cast e.target;
|
||||||
|
el.classList.add("disabled");
|
||||||
|
|
||||||
|
var req = new haxe.Http("/api/user/register");
|
||||||
|
req.addParameter("firstName", state.firstName);
|
||||||
|
req.addParameter("lastName", state.lastName);
|
||||||
|
req.addParameter("email", state.email);
|
||||||
|
req.addParameter("password", state.password);
|
||||||
|
req.addParameter("redirecturl", props.redirectUrl);
|
||||||
|
if(props.phoneRequired) req.addParameter("phone", state.phone);
|
||||||
|
|
||||||
|
req.onData = req.onError = function(d){
|
||||||
|
var d = req.responseData;
|
||||||
|
el.classList.remove("disabled");
|
||||||
|
var d = haxe.Json.parse(d);
|
||||||
|
if (Reflect.hasField(d, "error")) setError(d.error.message);
|
||||||
|
if (Reflect.hasField(d, "success")) js.Browser.window.location.href = props.redirectUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
req.request(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setError(err:String){
|
||||||
|
this.setState(cast {error:err});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package test;
|
||||||
|
|
||||||
|
import js.Browser;
|
||||||
|
import utest.Runner;
|
||||||
|
import utest.ui.Report;
|
||||||
|
import test.utils.TestCartUtils;
|
||||||
|
|
||||||
|
class TestAll
|
||||||
|
{
|
||||||
|
public static function main()
|
||||||
|
{
|
||||||
|
var runner = new Runner();
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
runner.addCase(new TestCartUtils());
|
||||||
|
|
||||||
|
Report.create(runner);
|
||||||
|
runner.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"contractTaxName": null,
|
||||||
|
"desc": "Des grattons fait avec du cochon gascon, élevé en plein air et selon les préceptes de l'agriculture biologique. Meilleur que des rillettes!",
|
||||||
|
"vatValue": 0.234597156398104,
|
||||||
|
"price": 4.5,
|
||||||
|
"name": "Gratton de porc noir",
|
||||||
|
"type": 0,
|
||||||
|
"contractTax": null,
|
||||||
|
"contractId": 4672,
|
||||||
|
"orderable": true,
|
||||||
|
"hasFloatQt": false,
|
||||||
|
"id": 56610,
|
||||||
|
"qt": 190,
|
||||||
|
"unitType": 2,
|
||||||
|
"ref": "GRA-1",
|
||||||
|
"vat": 5.5,
|
||||||
|
"categories": null,
|
||||||
|
"stock": null,
|
||||||
|
"organic": false,
|
||||||
|
"image": "/img/taxo/cat10.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"contractTaxName": null,
|
||||||
|
"desc": "Un pâté savoureux qui se mange sans modération! Date de péremption : 1 Avril 2019 Lot : L16AV01P Poids : 200gr Ingrédients : Viande de porc issue de l’agriculture biologique, Sel, Poivre",
|
||||||
|
"vatValue": 0.297156398104265,
|
||||||
|
"price": 5.7,
|
||||||
|
"name": "Pâté de porc noir",
|
||||||
|
"type": 0,
|
||||||
|
"contractTax": null,
|
||||||
|
"contractId": 4672,
|
||||||
|
"orderable": true,
|
||||||
|
"hasFloatQt": false,
|
||||||
|
"id": 56611,
|
||||||
|
"qt": 1,
|
||||||
|
"unitType": 0,
|
||||||
|
"ref": "PAPN-1",
|
||||||
|
"vat": 5.5,
|
||||||
|
"categories": null,
|
||||||
|
"stock": null,
|
||||||
|
"organic": false,
|
||||||
|
"image": "/file/2521_6a77c954191e5a2d8cd5a5d8cade4f6b.PNG"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package test.utils;
|
||||||
|
|
||||||
|
import sys.io.File.getContent;
|
||||||
|
import haxe.Json.parse;
|
||||||
|
import utest.Assert;
|
||||||
|
import utils.CartUtils;
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
class TestCartUtils
|
||||||
|
{
|
||||||
|
static var products:Array<ProductInfo>;
|
||||||
|
public function new() {
|
||||||
|
var http = new haxe.Http("localhost/js/test/mocks.json?format=json");
|
||||||
|
|
||||||
|
products = parse(getContent("js/test/mocks.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddToCart()
|
||||||
|
{
|
||||||
|
var order = {
|
||||||
|
products: [{
|
||||||
|
product: products[0],
|
||||||
|
quantity: 1
|
||||||
|
}],
|
||||||
|
total: products[0].price
|
||||||
|
};
|
||||||
|
|
||||||
|
var newProduct = products[1];
|
||||||
|
|
||||||
|
order = CartUtils.addToCart(order, newProduct, 1);
|
||||||
|
Assert.equals(order.products.length, 2);
|
||||||
|
Assert.equals(order.total, products[0].price + products[1].price);
|
||||||
|
order = CartUtils.addToCart(order, newProduct, 3);
|
||||||
|
Assert.equals(order.products[1].quantity, 4);
|
||||||
|
Assert.equals(order.total, products[0].price + 4 * products[1].price);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRemoveFromCart()
|
||||||
|
{
|
||||||
|
var order = {
|
||||||
|
products: [{
|
||||||
|
product: products[0],
|
||||||
|
quantity: 5
|
||||||
|
}, {
|
||||||
|
product: products[1],
|
||||||
|
quantity: 3
|
||||||
|
}],
|
||||||
|
total: 5 * products[0].price + 3 * products[1].price
|
||||||
|
};
|
||||||
|
|
||||||
|
order = CartUtils.removeFromCart(order, products[0], 2);
|
||||||
|
Assert.equals(order.products[0].quantity, 3);
|
||||||
|
Assert.equals(order.total, 3 * products[0].price + 3 * products[1].price);
|
||||||
|
order = CartUtils.removeFromCart(order, products[1]);
|
||||||
|
Assert.equals(order.products.length, 1);
|
||||||
|
Assert.equals(order.total, 3 * products[0].price);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package utils;
|
||||||
|
|
||||||
|
import Math;
|
||||||
|
using Lambda;
|
||||||
|
|
||||||
|
import Common;
|
||||||
|
|
||||||
|
class CartUtils {
|
||||||
|
public static function addToCart(order:OrderSimple, productToAdd:ProductInfo, quantity:Int):OrderSimple {
|
||||||
|
var products = order.products.copy();
|
||||||
|
var total = order.total;
|
||||||
|
|
||||||
|
var existingProduct = products.find(function(p) {
|
||||||
|
return p.product.id == productToAdd.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingProduct == null)
|
||||||
|
products.push({
|
||||||
|
product: productToAdd,
|
||||||
|
quantity: quantity
|
||||||
|
});
|
||||||
|
else
|
||||||
|
existingProduct.quantity += quantity;
|
||||||
|
|
||||||
|
total += quantity * productToAdd.price;
|
||||||
|
total = Math.round(total * 100) / 100; // to avoid calculation errors
|
||||||
|
|
||||||
|
return {
|
||||||
|
products: products,
|
||||||
|
total: total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function removeFromCart(order:OrderSimple, productToRemove:ProductInfo, ?quantity:Int):OrderSimple {
|
||||||
|
var products = order.products.copy();
|
||||||
|
var total = order.total;
|
||||||
|
|
||||||
|
var existingProduct = products.find(function(p) {
|
||||||
|
return p.product.id == productToRemove.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (quantity == null)
|
||||||
|
quantity = existingProduct.quantity;
|
||||||
|
|
||||||
|
if (existingProduct == null)
|
||||||
|
throw "Can't remove a non existing product";
|
||||||
|
else if (quantity >= existingProduct.quantity)
|
||||||
|
products.remove(existingProduct)
|
||||||
|
else
|
||||||
|
existingProduct.quantity -= quantity;
|
||||||
|
|
||||||
|
if (products.length == 0)
|
||||||
|
total = 0;
|
||||||
|
else {
|
||||||
|
total -= Math.min(quantity, existingProduct.quantity) * productToRemove.price;
|
||||||
|
total = Math.round(total * 100) / 100; // to avoid calculation errors
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
products: products,
|
||||||
|
total: total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package utils;
|
||||||
|
|
||||||
|
import haxe.Json;
|
||||||
|
import js.Promise;
|
||||||
|
import js.html.XMLHttpRequest;
|
||||||
|
|
||||||
|
@:enum abstract HttpMethod(String) to String {
|
||||||
|
var POST = 'POST';
|
||||||
|
var GET = 'GET';
|
||||||
|
var HEAD = 'HEAD';
|
||||||
|
var PUT = 'PUT';
|
||||||
|
var DELETE = 'DELETE';
|
||||||
|
var TRACE = 'TRACE';
|
||||||
|
var OPTIONS = 'OPTIONS';
|
||||||
|
var CONNECT = 'CONNECT';
|
||||||
|
var PATCH = 'GET';
|
||||||
|
}
|
||||||
|
|
||||||
|
@:enum abstract FetchFormat(String) from String to String {
|
||||||
|
var PLAIN_TEXT = "text/plain";
|
||||||
|
var JSON = "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
//json version of a tink.core.Error
|
||||||
|
typedef ErrorInfos = {error:{code:Int,message:String,stack:String}}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage HTTP request to a REST API.
|
||||||
|
*
|
||||||
|
* POST requests can only have a single JSON object (payload)
|
||||||
|
*/
|
||||||
|
class HttpUtil
|
||||||
|
{
|
||||||
|
static public function fetch(
|
||||||
|
url: String,
|
||||||
|
?method: HttpMethod = GET,
|
||||||
|
?params: Dynamic = null,
|
||||||
|
?accept: FetchFormat = PLAIN_TEXT,
|
||||||
|
?contentType: String = JSON
|
||||||
|
): Promise<Dynamic> {
|
||||||
|
|
||||||
|
return new Promise(function(resolve: Dynamic->Void, reject) {
|
||||||
|
var data: String = null;
|
||||||
|
if (params != null)
|
||||||
|
{
|
||||||
|
if (params.body != null){
|
||||||
|
data = Json.stringify(params.body);
|
||||||
|
} else if(method==POST) {
|
||||||
|
data = Json.stringify(params);
|
||||||
|
} else {
|
||||||
|
url += (url.indexOf('?') > -1) ? '&' : '?';
|
||||||
|
url += objToString(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var http = new XMLHttpRequest();
|
||||||
|
http.open(method, url, true);
|
||||||
|
|
||||||
|
if (contentType != null && contentType.length > 0)
|
||||||
|
http.setRequestHeader("Content-type", contentType);
|
||||||
|
|
||||||
|
if (accept != null)
|
||||||
|
http.setRequestHeader("Accept", accept);
|
||||||
|
|
||||||
|
http.onreadystatechange = function() {
|
||||||
|
//trace("readystate",http.readyState, http.status);
|
||||||
|
if (http.readyState == 4){
|
||||||
|
switch (http.status){
|
||||||
|
case 200:
|
||||||
|
switch (accept){
|
||||||
|
case JSON:
|
||||||
|
try {
|
||||||
|
var json = Json.parse(http.responseText);
|
||||||
|
resolve(json);
|
||||||
|
} catch (err: Dynamic){
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
resolve(http.responseText);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 204:
|
||||||
|
resolve(true);
|
||||||
|
|
||||||
|
default:
|
||||||
|
reject(http.responseText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
http.send(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function objToString(obj: Dynamic): String
|
||||||
|
{
|
||||||
|
var str = "";
|
||||||
|
var cpt = 0;
|
||||||
|
for (key in Reflect.fields(obj))
|
||||||
|
{
|
||||||
|
var value: Dynamic = Reflect.field(obj, key);
|
||||||
|
if (value == null) continue;
|
||||||
|
|
||||||
|
if (cpt++ > 0)
|
||||||
|
str += "&";
|
||||||
|
|
||||||
|
if (Std.is(value, Array) && value.length > 0)
|
||||||
|
str += '$key=${value.join(";")}';
|
||||||
|
else if (Std.string(value) != "") // String / Int / Float
|
||||||
|
str += '$key=${StringTools.trim(Std.string(value))}';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This is where the compiled templates are.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This is where the compiled templates are.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This is where the compiled templates are.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<h2>Coordonnées</h2>
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<div style="font-weight:bold;font-size:120%;">::member.firstName:: ::member.lastName::
|
||||||
|
::if member.firstName2!=null && member.lastName2!=null::
|
||||||
|
& ::member.firstName2:: ::member.lastName2::
|
||||||
|
|
||||||
|
::end::</div>
|
||||||
|
|
||||||
|
$$nullSafe(::member.address1::)<br/>
|
||||||
|
$$nullSafe(::member.address2::)<br/>
|
||||||
|
$$nullSafe(::member.zipCode::)
|
||||||
|
$$nullSafe(::member.city::)<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
::if member.phone!=null::
|
||||||
|
Tel : ::member.phone::<br/>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if member.email!=null::
|
||||||
|
E-mail : <a href="mailto: ::member.email::">::member.email::</a><br/>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if member.email2!=null::
|
||||||
|
E-mail : <a href="mailto: ::member.email2::">::member.email2::</a><br/>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<h2>::_("Payments")::</h2>
|
||||||
|
<div class="article">
|
||||||
|
<table class="table table-condensed">
|
||||||
|
|
||||||
|
<!-- total -->
|
||||||
|
<tr style="font-size:1.3em;">
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td class="text-right">::_("Balance"):: :</td>
|
||||||
|
::if balance<0::
|
||||||
|
::set color = "C00"::
|
||||||
|
::else::
|
||||||
|
::set color = "0C0"::
|
||||||
|
::end::
|
||||||
|
<td style="color:#::color::;">
|
||||||
|
::if balance<0::
|
||||||
|
-
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td style="color:#::color::;">
|
||||||
|
::Math.abs(balance):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td colspan="2">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::foreach t rb.current()::
|
||||||
|
<tr>
|
||||||
|
$$operation(::t::)
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<tr >
|
||||||
|
<th colspan="5" class="text-center">
|
||||||
|
<ul class="pagination">
|
||||||
|
::if rb.paginationStartPage != 1::
|
||||||
|
<li>
|
||||||
|
<a href="?page=::rb.paginationStartPage-1::">
|
||||||
|
<span class="glyphicon glyphicon-backward"></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
::end::
|
||||||
|
::foreach i loopList(rb.paginationStartPage,rb.paginationEndPage)::
|
||||||
|
::if rb.page==i::
|
||||||
|
<li class="active" ><a href="?page=::i::">::i::</a></li>
|
||||||
|
::else::
|
||||||
|
<li><a href="?page=::i::">::i::</a></li>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
::if rb.paginationEndPage < rb.pages + 1::
|
||||||
|
<li>
|
||||||
|
<a href="?page=::rb.paginationEndPage::">
|
||||||
|
<span class="glyphicon glyphicon-forward"></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
::end::
|
||||||
|
</ul>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
::use 'admin/design.mtt'::
|
||||||
|
|
||||||
|
<div id="content" class="col-md-8">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body">
|
||||||
|
<h1>H1 Title</h1>
|
||||||
|
<h2>H2 Title</h2>
|
||||||
|
<h3>H3 Title</h3>
|
||||||
|
<h4>H4 Title</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body">
|
||||||
|
<h2>
|
||||||
|
Titre H2
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
Donec ullamcorper venenatis augue, vitae tempor quam viverra at.
|
||||||
|
Sed faucibus ipsum sit amet turpis consectetur finibus. Nulla facilisi.
|
||||||
|
Vivamus consequat vestibulum blandit. Nunc laoreet tellus ac lorem commodo hendrerit.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Nulla gravida, libero et scelerisque convallis, nibh nisi ultrices nulla, sed faucibus sapien augue et turpis.
|
||||||
|
Nunc quis tempor dolor. Duis elementum nisl eu dolor malesuada convallis.
|
||||||
|
Sed id tellus euismod, eleifend neque ac, iaculis odio. Cras euismod odio quis elit elementum rhoncus.
|
||||||
|
Mauris in elementum ex, ut scelerisque leo. Suspendisse viverra mi non neque vulputate, in fermentum sapien sodales.
|
||||||
|
Sed in laoreet ante. Suspendisse non velit sit amet lectus consequat ornare sit amet quis ipsum.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="well text-center">
|
||||||
|
Nulla gravida, libero et scelerisque convallis, nibh nisi ultrices nulla, sed faucibus sapien augue et turpis.
|
||||||
|
Nunc quis tempor dolor. Duis elementum nisl eu dolor malesuada convallis.
|
||||||
|
<br/>
|
||||||
|
<a href="#" class="btn btn-primary">Big Button</a>
|
||||||
|
<a href="#" class="btn btn-default">Big Button</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Panel heading without title</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
Panel content
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
::use 'admin/design.mtt'::
|
||||||
|
|
||||||
|
|
||||||
|
<h3>Console d'administration de Cagette.net</h3>
|
||||||
|
<p>
|
||||||
|
::hDate(now)::
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
<div class="panel panel-warning">
|
||||||
|
|
||||||
|
<div class="panel-heading">
|
||||||
|
<span class="panel-title"><a href="/admin">Administration</a></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="nav nav-tabs nav-stacked" >
|
||||||
|
<li><a href="/db">Base de données</a> </li>
|
||||||
|
<li><a href="/admin/taxo">Dictionnaire de produits</a> </li>
|
||||||
|
<li><a href="/admin/errors">Errors</a> </li>
|
||||||
|
<li><a href="/admin/smtp">Conf. Email</a> </li>
|
||||||
|
<li><a href="/admin/emails">Emails</a> </li>
|
||||||
|
<!--<li><a href="/admin/plugins">Plugins</a> </li>-->
|
||||||
|
::if nav!=null::
|
||||||
|
::foreach i nav::
|
||||||
|
<li><a href="::i.link::">::i.name::</a> </li>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="article">
|
||||||
|
::raw __content__::
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
::use 'admin/design.mtt'::
|
||||||
|
<div class="span12">
|
||||||
|
<div class="article">
|
||||||
|
<h1>Emails</h1>
|
||||||
|
|
||||||
|
<p>::_("Emails are temporarily stored in a buffer, waiting to be sent by the cron")::</p>
|
||||||
|
<p>
|
||||||
|
<h3>::num:: unsent emails</h3>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
::set url = "/admin/emails"::
|
||||||
|
|
||||||
|
$$browse(::browser::,::url::)
|
||||||
|
|
||||||
|
<table class="table table-bordered table-striped table-condensed table-hover">
|
||||||
|
<tr>
|
||||||
|
<th>id</th>
|
||||||
|
<th>date</th>
|
||||||
|
<th>tries</th>
|
||||||
|
<th>title</th>
|
||||||
|
<th>html</th>
|
||||||
|
<th>status</th>
|
||||||
|
</tr>
|
||||||
|
<tr ::repeat e browser.current()::>
|
||||||
|
<td>::e.id::</td>
|
||||||
|
<td>::e.cdate::</td>
|
||||||
|
::if(e.tries==0)::
|
||||||
|
<td class="warning">::e.tries::</td>
|
||||||
|
::else::
|
||||||
|
<td class="danger">::e.tries::</td>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<td>::e.title::</td>
|
||||||
|
<td>
|
||||||
|
::if e.htmlBody != null::
|
||||||
|
<a href="#" onclick="$(this).next().toggle(); return false;">[show]</a>
|
||||||
|
<div style="display:none;">
|
||||||
|
::raw e.htmlBody::
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>::e.rawStatus::</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
$$browse(::browser::,::url::)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
::use 'admin/design.mtt'::
|
||||||
|
<div class="span12">
|
||||||
|
<div class="article">
|
||||||
|
<h1>Errors</h1>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<form action="/admin/errors" method="GET">
|
||||||
|
User :
|
||||||
|
<input type="text" name="user" class="tid_userSearch" tid_optional="1" ::attr tid_value if(u!=null) u.name+"/"+u.id::/><br/>
|
||||||
|
|
||||||
|
Error :
|
||||||
|
<input type="text" name="like" value="::like::" /><br/>
|
||||||
|
|
||||||
|
<input type="submit" value="Search" class="btn btn-default btn-sm"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
Date serveur : ::now::<br/>
|
||||||
|
|
||||||
|
<a href="/admin/errors?empty=1" class="btn btn-default btn-sm" $$confirm()>Vider la table</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/js/rgraph/RGraph.common.core.js" ></script>
|
||||||
|
<script type="text/javascript" src="/js/rgraph/RGraph.bar.js" ></script>
|
||||||
|
<canvas id="graph" width="900" height="150">[No canvas support]</canvas>
|
||||||
|
<script>
|
||||||
|
window.onload = function ()
|
||||||
|
{
|
||||||
|
|
||||||
|
var datas = [];
|
||||||
|
var labels = [];
|
||||||
|
::foreach e errorsStats::
|
||||||
|
datas.push(::e.c::);
|
||||||
|
labels.push( '::e.day::' );
|
||||||
|
::end::
|
||||||
|
|
||||||
|
var bar = new RGraph.Bar('graph', datas);
|
||||||
|
bar.Set('chart.labels', labels);
|
||||||
|
bar.Set('chart.colors', ['#C00']);
|
||||||
|
bar.Set('chart.title', 'Last errors');
|
||||||
|
bar.Set('chart.labels.above', true);
|
||||||
|
bar.Set('chart.background.barcolor1', 'white');
|
||||||
|
bar.Set('chart.background.barcolor2', 'white');
|
||||||
|
bar.Set('chart.background.grid', true);
|
||||||
|
bar.Set('chart.gutter.left', 60);
|
||||||
|
bar.Set('chart.gutter.bottom', 40);
|
||||||
|
bar.Set('chart.text.angle', 45);
|
||||||
|
bar.Draw();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
::set url = "/admin/errors"::
|
||||||
|
|
||||||
|
$$browse(::browser::,::url::)
|
||||||
|
<table class="table table-bordered table-striped table-condensed table-hover">
|
||||||
|
<tr>
|
||||||
|
<th>id</th>
|
||||||
|
<th>date</th>
|
||||||
|
<th>Error</th>
|
||||||
|
<th>infos</th>
|
||||||
|
<th>user id</th>
|
||||||
|
<th>URL</th>
|
||||||
|
</tr>
|
||||||
|
<tr ::repeat r browser.current()::>
|
||||||
|
<td>::r.id::</td>
|
||||||
|
<td>::r.date::</td>
|
||||||
|
|
||||||
|
::set e = r.error.split('Called')::
|
||||||
|
<td>
|
||||||
|
::e.shift()::
|
||||||
|
::if e.length > 0::
|
||||||
|
<a href="#" onclick="$(this).next().toggle(); return false;">[stack]</a>
|
||||||
|
<div style="display:none;">
|
||||||
|
<pre>Called::e.join("Called")::</pre>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" onclick="$(this).next().toggle(); return false;">[infos]</a>
|
||||||
|
<div style="display:none;">
|
||||||
|
<b>IP: </b>::r.ip::<br/>
|
||||||
|
<b>User-Agent: </b>::r.userAgent::
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span ::cond r.uid!=null::>
|
||||||
|
<a href="/p/hosted/user/::r.uid::">::r._user.getName()::</a>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>::r.url::</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
$$browse(::browser::,::url::)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
::use 'admin/design.mtt'::
|
||||||
|
|
||||||
|
|
||||||
|
<h3>::key::</h3>
|
||||||
|
<p>
|
||||||
|
Du ::hDate(from):: au ::hDate(to)::<br/>
|
||||||
|
Total : <b>::total::</b><br/>
|
||||||
|
Moyenne : <b>::averageValue::</b>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<script type="text/javascript" src="/js/rgraph/RGraph.common.core.js" ></script>
|
||||||
|
<script type="text/javascript" src="/js/rgraph/RGraph.bar.js" ></script>
|
||||||
|
<canvas id="graph" width="900" height="150">[No canvas support]</canvas>
|
||||||
|
<script>
|
||||||
|
window.onload = function ()
|
||||||
|
{
|
||||||
|
|
||||||
|
var datas = [];
|
||||||
|
var labels = [];
|
||||||
|
::foreach d data::
|
||||||
|
datas.push(::d.value::);
|
||||||
|
labels.push( '::d.date.toString().substr(0,10)::' );
|
||||||
|
::end::
|
||||||
|
|
||||||
|
var bar = new RGraph.Bar('graph', datas);
|
||||||
|
bar.Set('chart.labels', labels);
|
||||||
|
bar.Set('chart.colors', ['#CC0']);
|
||||||
|
bar.Set('chart.title', '::key::');
|
||||||
|
bar.Set('chart.labels.above', true);
|
||||||
|
bar.Set('chart.background.barcolor1', 'white');
|
||||||
|
bar.Set('chart.background.barcolor2', 'white');
|
||||||
|
bar.Set('chart.background.grid', true);
|
||||||
|
bar.Set('chart.gutter.left', 60);
|
||||||
|
bar.Set('chart.gutter.bottom', 40);
|
||||||
|
bar.Set('chart.text.angle', 45);
|
||||||
|
bar.Draw();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-striped">
|
||||||
|
::foreach d data::
|
||||||
|
<tr>
|
||||||
|
<td>::hDate(d.date)::</td>
|
||||||
|
<td>::d.value::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
::use 'admin/design.mtt'::
|
||||||
|
|
||||||
|
<h2>Plugins</h2>
|
||||||
|
<p>
|
||||||
|
L'installation des plugins se fait sur la machine de développement.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
::foreach p plugins::
|
||||||
|
<tr>
|
||||||
|
<th>Nom</th>
|
||||||
|
<th>Installé</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>::p.name::</td>
|
||||||
|
<td>
|
||||||
|
::set i = p.isInstalled()::
|
||||||
|
$$bool(::i::)
|
||||||
|
|
||||||
|
::if i==false::
|
||||||
|
<a href="/admin/plugins/install/::p.getName()::" class="btn btn-default">Installer</a>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
::use 'admin/design.mtt'::
|
||||||
|
|
||||||
|
<h2>Dictionnaire de produits</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Cagette.net inclut maintenant un dictionnaire de produits qui permet d'identifier
|
||||||
|
facilement les produits que vous saisissez.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
::foreach c categ::
|
||||||
|
|
||||||
|
<img src="/img/taxo/cat::c.id::.png" style="float:right;width:100px;border:1px solid #666;" />
|
||||||
|
|
||||||
|
<h4>#::c.id:: <a href="/db/TxpCategory/edit/::c.id::" target="_blank">::c.name::</a></h4>
|
||||||
|
::foreach sc c.getSubCategories()::
|
||||||
|
<p style="margin-left:12px;">
|
||||||
|
<b><a href="/db/TxpSubCategory/edit/::sc.id::" target="_blank">::sc.name::</a></b>
|
||||||
|
<br/>
|
||||||
|
::foreach p sc.getProducts()::
|
||||||
|
<a href="/db/TxpProduct/edit/::p.id::" target="_blank">::p.name::</a>,
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</p>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
::user._amap.name::
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
::if user._amap._image!=null::
|
||||||
|
::if user._amap.extUrl!=null && user._amap.extUrl!=""::
|
||||||
|
<a href="::user._amap.extUrl::"><img src="::file(user._amap._image)::" style="margin:auto;display: block;width: 100%;" class="thumbnail" /><br/></a>
|
||||||
|
::else::
|
||||||
|
<img src="::file(user._amap._image)::" style="margin:auto;display: block;width: 100%;" class="thumbnail" /><br/>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::raw nl2br(user.getAmap().txtIntro)::
|
||||||
|
<br/>
|
||||||
|
::if user.isAmapManager()::
|
||||||
|
$$edit(::_("Edit")::,amap/edit)
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<b>::_("Main contact"):: :</b><br/>
|
||||||
|
$$contact(::user.getAmap()._contact::)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
::foreach c contracts::
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="article">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3 text-center">
|
||||||
|
<div ::cond c._vendor._image!=null:: style="background-image:url('::file(c._vendor._image)::');width:100px;height:100px;" class="productImg"/>
|
||||||
|
<p>
|
||||||
|
<b>::c._vendor.name::</b><br/>
|
||||||
|
::c._vendor.city:: (::c._vendor.zipCode::)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h4><a href="/contract/view/::c.id::">::c.name::</a></h4>
|
||||||
|
|
||||||
|
<div style="font-size:0;">
|
||||||
|
::foreach p c.getProductsPreview(5)::
|
||||||
|
<div data-toggle="tooltip" data-placement="top" title="::p.name::" style="background-image:url('::p.getImage()::')" class="productImg"/>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
::if c._contact!=null::
|
||||||
|
<b>::_("Coordinator"):: :</b>
|
||||||
|
<br/>
|
||||||
|
$$contact(::c._contact::)
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
::use 'amapadmin/design.mtt'::
|
||||||
|
|
||||||
|
|
||||||
|
<h2>::_("Choose a logo")::</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<form action="" method="POST" enctype="multipart/form-data">
|
||||||
|
<input name="image" type="file" class="btn btn-default"/>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
::_("Format : JPG, GIF or PNG"):: <br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<input type="submit" value="::_("Send")::" class="btn btn-primary" />
|
||||||
|
</form>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6" ::cond image!=null:: >
|
||||||
|
|
||||||
|
<img src="::file(image)::" class="img-thumbnail" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
::use 'amapadmin/design.mtt'::
|
||||||
|
|
||||||
|
<h3>::user._amap.name::</h3>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p>
|
||||||
|
<strong>::membersNum::</strong> ::_("members")::.<br/>
|
||||||
|
<strong>::contractsNum::</strong> ::_("contracts in progress")::.<br/>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
$$edit(::_("Edit properties")::,/amap/edit)
|
||||||
|
|
||||||
|
<a href="/amapadmin/addimage/" class="btn btn-default btn-sm"> <span class="glyphicon glyphicon-picture"></span> ::_("Choose a logo")::</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
::raw _("Your group has a public page, You can use it as a micro-website.<br/>Copy-paste the address of this page and share it !")::
|
||||||
|
<br/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p class="linkToShare">
|
||||||
|
<a href="http://::HOST::/group/::user._amap.id::">
|
||||||
|
http://::HOST::/group/::user._amap.id::
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/group/::user._amap.id::" class="btn btn-default"><span class="glyphicon glyphicon-chevron-right"></span> ::_("Display")::</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
|
||||||
|
::if user._amap._image!=null::
|
||||||
|
<img src="::file(user._amap._image)::" style="width: 100%;" class="thumbnail" />
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
<div class="panel panel-warning">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<span class="panel-title"><a href="/amapadmin">::_("My group")::</a></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul id="subnav" class="nav nav-tabs nav-stacked" >
|
||||||
|
|
||||||
|
<li><a href="/amapadmin/rights/">::_("Access rights")::</a></li>
|
||||||
|
<li><a href="/amapadmin/vatRates/">::_("VAT rates")::</a></li>
|
||||||
|
<li><a href="/amapadmin/categories/">::_("Categories")::</a></li>
|
||||||
|
<li><a href="/amapadmin/currency/">::_("Currency")::</a></li>
|
||||||
|
::if nav!=null::
|
||||||
|
|
||||||
|
::foreach i nav::
|
||||||
|
<li><a href="::i.link::">::i.name::</a> </li>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::end::
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="article">
|
||||||
|
::raw __content__::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
::use 'amapadmin/design.mtt'::
|
||||||
|
|
||||||
|
<h3>::_("Management of rights and access")::</h3>
|
||||||
|
|
||||||
|
$$insert(::_("Add a right")::,/amapadmin/editRight)
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th>::_("Name")::</th>
|
||||||
|
<th>::_("Rights")::</th>
|
||||||
|
<th>::_("Actions")::</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::foreach u users::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::u._user.getName()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ul ::cond u._rights!=null::>
|
||||||
|
::foreach r u._rights::
|
||||||
|
<li>::u.getRightName(r)::</li>
|
||||||
|
::end::
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
$$insert(::_("Modify")::,/amapadmin/editRight/::u.userId::)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html lang="::LANG::">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" xml:lang="::LANG::" lang="::LANG::" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>
|
||||||
|
::if pageTitle!=null::
|
||||||
|
::pageTitle::
|
||||||
|
::else::
|
||||||
|
::NAME::
|
||||||
|
::end::
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<!-- Cagette CSS -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/::getVariable('css')::/style.css"/>
|
||||||
|
<!--
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/::getVariable('css')::/shop.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/::getVariable('css')::/map.css"/>-->
|
||||||
|
|
||||||
|
<!-- JQUERY -->
|
||||||
|
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<!-- BOOTSTRAP -->
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css" />
|
||||||
|
|
||||||
|
<!-- bootstrap datepicker -->
|
||||||
|
<script type="text/javascript" src="/bower_components/moment/min/moment.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/bower_components/moment/locale/fr.js"></script>
|
||||||
|
<script type="text/javascript" src="/bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css" />
|
||||||
|
|
||||||
|
<!-- Leaflet CSS -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css"
|
||||||
|
integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ=="
|
||||||
|
crossorigin=""/>
|
||||||
|
|
||||||
|
<!-- typeahead
|
||||||
|
<script type="text/javascript" src="/bower_components/typeahead.js/dist/typeahead.jquery.js"></script> -->
|
||||||
|
|
||||||
|
<!-- font awesome -->
|
||||||
|
<script src="https://use.fontawesome.com/97d97e0dab.js"></script>
|
||||||
|
|
||||||
|
<!-- polyfill for IE11 : Object.assign() doesnt exists in IE11 , error in libs.js -->
|
||||||
|
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Haxe JS + libs bundled with browserify -->
|
||||||
|
<script type="text/javascript" src="/js/::getVariable('js')::/libs.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/::getVariable('js')::/app.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
::if container==null:: ::set container="container":: ::end::
|
||||||
|
<body class="::container::">
|
||||||
|
|
||||||
|
::if DEBUG::
|
||||||
|
<div style="position: absolute;background: #666;padding: 8px;left: 0;color: #FF0;">DEBUG ::session.lang::</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if HOST.substr(0,3)=="pp."::
|
||||||
|
<div style="position: absolute;background: #F60;padding: 8px;left: 0;color: #FF0;">PREPROD</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- Modal window -->
|
||||||
|
<div class="modal" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h2 class="modal-title" id="myModalLabel"></h2>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer" style="clear:both;">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||||
|
<span class='glyphicon glyphicon-remove'></span> ::_("Close")::
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::raw __content__::
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
//Google Analytics
|
||||||
|
var _gaq = _gaq || [];
|
||||||
|
_gaq.push(['_setAccount', 'UA-17932663-2']);
|
||||||
|
_gaq.push(['_trackPageview']);
|
||||||
|
(function() {
|
||||||
|
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||||
|
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||||
|
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
//enable BS3 Tooltips & popovers
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
$('[data-toggle="popover"]').popover();
|
||||||
|
|
||||||
|
//init lang in js App
|
||||||
|
_.LANG = "::session.lang::";
|
||||||
|
|
||||||
|
//tutorials
|
||||||
|
::if tuto!=null::
|
||||||
|
_.getTuto("::tuto.name::",::tuto.step::);
|
||||||
|
::end::
|
||||||
|
|
||||||
|
_.antiDoubleClick();
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
//tracking 24x7
|
||||||
|
var rumMOKey='1d36c37d438ba069db210c73cb74308d';
|
||||||
|
(function(){
|
||||||
|
if(window.performance && window.performance.timing && window.performance.navigation) {
|
||||||
|
var site24x7_rum_beacon=document.createElement('script');
|
||||||
|
site24x7_rum_beacon.async=true;
|
||||||
|
site24x7_rum_beacon.setAttribute('src','//static.site24x7rum.com/beacon/site24x7rum-min.js?appKey='+rumMOKey);
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(site24x7_rum_beacon);
|
||||||
|
}
|
||||||
|
})(window)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<!-- used by the connector plugin -->
|
||||||
|
<!-- the modal window html is already in the default design -->
|
||||||
|
<script>
|
||||||
|
$("#myModal").find(".modal-body").html("::raw html::").removeClass("modal-lg");
|
||||||
|
$("#myModal").find(".modal-title").html("::raw title::");
|
||||||
|
::if btn!=null::
|
||||||
|
$("#myModal").find(".modal-footer").html("::raw btn::");
|
||||||
|
::end::
|
||||||
|
$("#myModal").modal();
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<div class="block">
|
||||||
|
::if user!=null::
|
||||||
|
::_("Hello"):: ::user.getName()::<br/>
|
||||||
|
|
||||||
|
<a href="/user/logout" class="btn btn-default btn-sm">::_("Log out")::</a>
|
||||||
|
::else::
|
||||||
|
|
||||||
|
::raw _("You already have an account?<br/>
|
||||||
|
Click here to log in")::<br/>
|
||||||
|
<a href="/user/login" class="btn btn-default btn-sm btn-primary">::_("Log in")::</a>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
::use 'amapadmin/design.mtt'::
|
||||||
|
|
||||||
|
<h2>::_("Categories")::</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
::raw _("Categories allow to sort your products per category, for example \"vegetables, meet, backery, etc.\"<br/> It is possible to create many groups of categories in order to sort products as we wish.")::
|
||||||
|
</p>
|
||||||
|
<p ::cond groups.length==0 ::>
|
||||||
|
::_("You currently have no category. Would you like to create the list of categories by default ?")::
|
||||||
|
<a href="/amapadmin/categories/generate" class="btn btn-default">::_("Create the list of default categories")::</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
::foreach g groups::
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Groupe "::g.name::"
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a href="/amapadmin/categories/editGroup/::g.id::" class="btn btn-default btn-xs">::_("Modify group")::</a>
|
||||||
|
<a href="/amapadmin/categories/deleteGroup/::g.id::?token=::token::" class="btn btn-default btn-xs" $$confirm()>::_("Remove group")::</a>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::foreach c g.getCategories()::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="tag" style="background: ::c.getColor()::;">::c.name::</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="/amapadmin/categories/edit/::c.id::" class="btn btn-default btn-xs">::_("Modify")::</a>
|
||||||
|
<a href="/amapadmin/categories/delete/::c.id::?token=::token::" class="btn btn-default btn-xs" $$confirm() >::_("Remove")::</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/amapadmin/categories/insert/::g.id::" class="btn btn-default btn-xs">::_("Add a category in this group")::</a>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<a href="/amapadmin/categories/insertGroup" class="btn btn-default">::_("Add a group")::</a>
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h2>::_("Last orders")::</h2>
|
||||||
|
|
||||||
|
::if constOrders.length>0::
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<!-- CONSTANT ORDERS -->
|
||||||
|
|
||||||
|
<h3>::_("CSA Contracts")::</h3>
|
||||||
|
|
||||||
|
::foreach c constOrders::
|
||||||
|
|
||||||
|
<h4>::c.contract.name::</h4>
|
||||||
|
<table class="table table-bordered table-condensed" >
|
||||||
|
<tr>
|
||||||
|
<th>::_("Qty||short version for quantity")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th><a href="#" data-toggle="tooltip" data-placement="top" title="::_('Unit price including taxes')::">::_("U.P||short version of price per unit")::</a></th>
|
||||||
|
<th>::_("Sub-total")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::_("Paid")::</th>
|
||||||
|
</tr>
|
||||||
|
::set total = 0::
|
||||||
|
::foreach m c.orders::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::raw m.smartQt::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" onclick="_.overlay('/shop/productInfo/::m.productId::','::m.productName::')">
|
||||||
|
<img src="::m.productImage::" style="width:32px;height:32px;" />
|
||||||
|
::m.productName::
|
||||||
|
</a>
|
||||||
|
::if m.userName2 != null::
|
||||||
|
<br /><span class="glyphicon glyphicon-refresh"></span> ::_("Order alternated with")::
|
||||||
|
::if user.id == m.userId::
|
||||||
|
::m.userName2::
|
||||||
|
::else::
|
||||||
|
::m.userName::
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.subTotal):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- frais -->
|
||||||
|
::if m.percentageValue!=null::
|
||||||
|
<a href="#" data-toggle="tooltip" data-placement="top" title="::m.percentageName:: : ::m.percentageValue:: %">
|
||||||
|
::formatNum(m.fees):: ::currency()::
|
||||||
|
</a>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- total -->
|
||||||
|
::formatNum(m.total):: ::currency()::
|
||||||
|
::set total = total + m.total::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.paid==true::
|
||||||
|
<span style="color:#00AA00;">::_("Paid")::</span>
|
||||||
|
::else::
|
||||||
|
<span style="color:#DD0000;">::_("Unpaid")::</span>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-right">
|
||||||
|
<b>Total :
|
||||||
|
::formatNum(total):: ::currency()::</b>
|
||||||
|
|
||||||
|
::set d = c.contract.getDistribs(false,null).length::
|
||||||
|
( soit ::formatNum(d*total):: ::currency():: pour ::d:: livraisons )
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- VARYING ORDERS -->
|
||||||
|
::if count(varOrders)>0::
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
::foreach d varOrders::
|
||||||
|
<h4>::_("Order delivered on"):: ::hDate(d.date)::</h4>
|
||||||
|
<table class="table table-bordered table-condensed" >
|
||||||
|
<tr>
|
||||||
|
<th>::_("Qty||short version for quantity")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th><a href="#" data-toggle="tooltip" data-placement="top" title="::_('Unit price including taxes')::">::_("U.P||short version of price per unit")::</a></th>
|
||||||
|
<th>::_("Sub-total")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::_("Paid")::</th>
|
||||||
|
</tr>
|
||||||
|
::set total = 0::
|
||||||
|
::foreach o d.orders::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::raw o.smartQt::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" onclick="_.overlay('/shop/productInfo/::o.productId::','::o.productName::')">
|
||||||
|
<img src="::o.productImage::" style="width:32px;height:32px;" />
|
||||||
|
::o.productName::
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(o.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(o.subTotal):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
$$nullSafe(::formatNum(o.fees)::)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(o.total):: ::currency()::
|
||||||
|
::set total = total + o.total::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if o.paid==true::
|
||||||
|
<span style="color:#00AA00;">::_("Paid")::</span>
|
||||||
|
::else::
|
||||||
|
<span style="color:#DD0000;">::_("Unpaid")::</span>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">
|
||||||
|
::if !user._amap.hasPayments()::
|
||||||
|
$$edit(Modifier cette commande,/contract/editOrderByDate/::d.date::)
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>TOTAL</td>
|
||||||
|
<td><b>::formatNum(total):: ::currency()::</b></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<h3>::_("Profile")::</h3>
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
$$contact(::user::)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="/account/quit?token=::token::" class="btn btn-default btn-sm" $$confirm(::_("Do you really want to quit this group ? You won't be a member anymore: you won't receive any new message and won't be able to see your previous orders in this group.")::)>
|
||||||
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
|
::_("Leave this group")::
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
$$edit(::_("Edit")::,/account/edit)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::if user.getAmap().hasPayments()::
|
||||||
|
<h3>::_("Payments")::</h3>
|
||||||
|
<div class="article">
|
||||||
|
<span style="font-size:1.3em;">
|
||||||
|
::_("Balance"):: :
|
||||||
|
::if userAmap.balance<0::
|
||||||
|
::set color = "C00"::
|
||||||
|
::else::
|
||||||
|
::set color = "0C0"::
|
||||||
|
::end::
|
||||||
|
<span style="color:#::color::;">
|
||||||
|
::userAmap.balance:: ::currency()::
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span style="margin-left:30px;">
|
||||||
|
<a href="/account/payments" class="btn btn-default btn-small">
|
||||||
|
<i class="fa fa-credit-card" aria-hidden="true"></i>
|
||||||
|
::_("Payments details")::
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
::if user.isAmapManager()::
|
||||||
|
<h3>::_("Tutorials")::</h3>
|
||||||
|
<div class="article">
|
||||||
|
<table class="table">
|
||||||
|
|
||||||
|
::foreach t tutos::
|
||||||
|
<tr>
|
||||||
|
<td>::t.name::</td>
|
||||||
|
<td>
|
||||||
|
::if t.completion==null::
|
||||||
|
<a href="/contract/?startTuto=::t.key::" class="btn btn-default btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-play"></span> ::_("Start")::
|
||||||
|
</a>
|
||||||
|
::else::
|
||||||
|
|
||||||
|
<a href="/contract/?stopTuto=::t.key::" class="btn btn-primary btn-xs">
|
||||||
|
<span class="glyphicon glyphicon-remove"></span> ::_("Stop")::
|
||||||
|
</a>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
::if stopTuto::
|
||||||
|
<div id="stopTuto" data-toggle="popover" title='::_("Tutorial stopped")::' data-placement="left" data-content="::_("You'll be able to restart it here.")::" ></div>
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
setTimeout(function() { $("#stopTuto").popover("show"); }, 1000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<h3>::_("Language")::</h3>
|
||||||
|
<div class="article">
|
||||||
|
::_("Display the interface in another language"):: (beta)
|
||||||
|
:
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
::raw langLinks::
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
::_("Current language is")::
|
||||||
|
<b>
|
||||||
|
::langText::
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<div class="col-md-12"><h2>::_("Choose a type of contract:")::</h2></div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<div style="text-align:center;margin-bottom:12px;">
|
||||||
|
<a href="/contract/insert/0" class="btn btn-default btn-lg">::raw _("Create a <b>CSA</b> contract")::</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
::_("It is a contract with a duration of one year or for one season. Products are always the same at each delivery")::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i>::raw _("Example: there is a contract for vegetables that is 1 year long. Jean takes a basket that costs 10€ per delivery, and Brigitte takes a basket that costs 15€ per delivery.")::</i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<div style="text-align:center;margin-bottom:12px;">
|
||||||
|
<a href="/contract/insert/1" class="btn btn-default btn-lg">::raw _("Create a contract with <b>variable orders</b>")::</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
::_("It is a contract in which members can change their orders at each delivery. There is a different order for each delivery.")::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<i>::raw _("Example: Jean orders a basket that costs 10€ for the first delivery, then nothing for the second delivery, then a basket for 20€ etc.")::</i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
<div id="content" class="col-md-8">
|
||||||
|
<div class="article">
|
||||||
|
<h2>::_("Order"):: ::c.name::</h2>
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
::_("For the delivery of the"):: <b>::hDate(distribution.date)::</b>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<p>
|
||||||
|
::raw _("As long as orders are open, you can come back on this page and modify your order.<br/>You can check any time your current orders on the following page <a href='/contract'>my account</a>.")::
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form name="order" id="order" method="POST" action="">
|
||||||
|
<input type="hidden" name="token" value="::token::"/>
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
<input type="hidden" name="distribution" value="::distribution.id::"/>
|
||||||
|
::end::
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Price")::</th>
|
||||||
|
::foreach d userOrders::
|
||||||
|
::if d.distrib==null::
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
::else::
|
||||||
|
<th>::dDate(d.distrib.date)::</th>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::if userOrders.length>0::
|
||||||
|
|
||||||
|
::foreach uo userOrders[0].datas::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img src="::uo.product.getImage()::" style="width:32px;height:32px;" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span ::cond uo.product.organic:: title ="Agriculture biologique" data-toggle="tooltip" data-placement="bottom">
|
||||||
|
<img src="/img/AB.png"/>
|
||||||
|
</span>
|
||||||
|
<a href="#" onclick="_.overlay('/shop/productInfo/::uo.product.id::','::escapeJS(uo.product.getName())::')">
|
||||||
|
::uo.product.getName()::
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::set price = uo.product.price + uo.product._contract.computeFees(uo.product.price)::
|
||||||
|
::formatNum(price):: ::currency()::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
::set i = repeat.uo.index::
|
||||||
|
|
||||||
|
::foreach d userOrders::
|
||||||
|
<td>
|
||||||
|
|
||||||
|
::set o = d.datas[i]::
|
||||||
|
|
||||||
|
::if o.order==null::
|
||||||
|
::set q = ""::
|
||||||
|
::else::
|
||||||
|
::set q = o.order.quantity::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
::if d.distrib==null::
|
||||||
|
<!-- contrat amap-->
|
||||||
|
<input type="text" class="form-control" name="::"d-p"+o.product.id::" id="product::o.product.id::" value="::q::" />
|
||||||
|
::else::
|
||||||
|
<input type="text" class="form-control" name="::"d"+d.distrib.id+"-p"+o.product.id::" id="product::o.product.id::" value="::q::" />
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::else::
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">::_("There is currently no open order")::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th colspan="3" class="text-right">Total</th>
|
||||||
|
|
||||||
|
::foreach d userOrders::
|
||||||
|
<th>
|
||||||
|
|
||||||
|
::set total=0::
|
||||||
|
::foreach o d.datas::
|
||||||
|
|
||||||
|
::if o.order!=null::
|
||||||
|
::set q = o.order.quantity::
|
||||||
|
::set price = o.product.price + o.product._contract.computeFees(o.product.price)::
|
||||||
|
|
||||||
|
::set total = total+(q*price)::
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::formatNum(total):: ::currency()::
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<i>::_("Tip: to remove an order please key-in the quantity 0")::</i><br/><br/>
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary" value="::_("Validate the order")::" name="submit" id="submit" />
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="article">
|
||||||
|
::_("Supplier:"):: <b>::c._vendor.name::</b>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
::_("Contact in charge:"):: $$contact(::c._contact::)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
<div id="content" class="col-md-8">
|
||||||
|
<div class="article">
|
||||||
|
<h2>::_("Order"):: ::c.name::</h2>
|
||||||
|
|
||||||
|
<!--::if distribution!=null::
|
||||||
|
::_("For the delivery of the"):: <b>::hDate(distribution.date)::</b>
|
||||||
|
::end::
|
||||||
|
<hr/>-->
|
||||||
|
<p>
|
||||||
|
::raw _("Key-in here your orders in advance for this supplier.<br/>You can check your current orders any time on the following page <a href=\"/contract\">my account</a>.<br/>Tip: to remove an order please key-in the quantity 0")::
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form name="order" id="order" method="POST" action="">
|
||||||
|
<input type="hidden" name="token" value="::token::"/>
|
||||||
|
|
||||||
|
<!--::if distribution!=null::
|
||||||
|
<input type="hidden" name="distribution" value="::distribution.id::"/>
|
||||||
|
::end::-->
|
||||||
|
|
||||||
|
<table class="table table-bordered table-striped" style="background:white;" >
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Price")::</th>
|
||||||
|
::foreach d userOrders::
|
||||||
|
::if d.distrib==null::
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
::else::
|
||||||
|
<th class="text-center">::dDate(d.distrib.date)::</th>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::if userOrders.length>0::
|
||||||
|
|
||||||
|
::foreach uo userOrders[0].datas::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img src="::uo.product.getImage()::" style="width:32px;height:32px;" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span ::cond uo.product.organic:: title ="Agriculture biologique" data-toggle="tooltip" data-placement="bottom">
|
||||||
|
<img src="/img/AB.png"/>
|
||||||
|
</span>
|
||||||
|
<a href="#" onclick="_.overlay('/shop/productInfo/::uo.product.id::','::escapeJS(uo.product.getName())::')">
|
||||||
|
::uo.product.getName()::
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::set price = uo.product.price + uo.product._contract.computeFees(uo.product.price)::
|
||||||
|
::formatNum(price):: ::currency()::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
::set i = repeat.uo.index::
|
||||||
|
|
||||||
|
::foreach d userOrders::
|
||||||
|
<td class="text-center">
|
||||||
|
|
||||||
|
::set o = d.datas[i]::
|
||||||
|
|
||||||
|
::if o.order==null::
|
||||||
|
::if d.distrib==null::
|
||||||
|
<!-- contrat amap-->
|
||||||
|
<input type="text" class="form-control" name="::"d-p"+o.product.id::" id="product::o.product.id::" value="" />
|
||||||
|
::else::
|
||||||
|
<input type="text" class="form-control" name="::"d"+d.distrib.id+"-p"+o.product.id::" id="product::o.product.id::" value="" />
|
||||||
|
::end::
|
||||||
|
::else::
|
||||||
|
::o.order.quantity::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::else::
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">::_("There is currently no open order")::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th colspan="3" class="text-right">Total</th>
|
||||||
|
|
||||||
|
::foreach d userOrders::
|
||||||
|
<th>
|
||||||
|
|
||||||
|
::set total=0::
|
||||||
|
::foreach o d.datas::
|
||||||
|
|
||||||
|
::if o.order!=null::
|
||||||
|
::set q = o.order.quantity::
|
||||||
|
::set price = o.product.price + o.product._contract.computeFees(o.product.price)::
|
||||||
|
|
||||||
|
::set total = total+(q*price)::
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::formatNum(total):: ::currency()::
|
||||||
|
</th>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary" value="::_("Validate the order")::" name="submit" id="submit" />
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="article">
|
||||||
|
<p>
|
||||||
|
::_("Supplier:"):: <b>::c._vendor.name::</b>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
::_("Contact in charge:"):: $$contact(::c._contact::)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
<div id="content" class="col-md-8">
|
||||||
|
<div class="article">
|
||||||
|
<h2>
|
||||||
|
::__("Modify the order delivered on ::date::",{date:hDate(date)})::
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
::raw _("You can modify here an existing order. If you would like to remove a product, just key-in the quantity 0.<br/>You may not be able to modify all elements as the order could be closed for some suppliers")::
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form name="order" id="order" method="POST" action="">
|
||||||
|
<input type="hidden" name="token" value="::token::"/>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("U.P.")::</th>
|
||||||
|
<th>::_("Sub-total")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::_("Quantity")::</th>
|
||||||
|
</tr>
|
||||||
|
::set total = 0::
|
||||||
|
::foreach o orders::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="#" onclick="_.overlay('/shop/productInfo/::o.productId::')">
|
||||||
|
<img src="::o.productImage::" style="width:32px;height:32px;" />
|
||||||
|
::o.quantity:: x ::o.productName::
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(o.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(o.subTotal):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
$$nullSafe(::formatNum(o.fees)::)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(o.total):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if o.canModify::
|
||||||
|
<input type="text" class="form-control" name="product::o.productId::" id="product::o.productId::" value="::o.quantity::" />
|
||||||
|
::else::
|
||||||
|
<span style="color:#AAA">::_("Order closed or already paid")::</span>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
::set total = total + o.total::
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th colspan="3"></th>
|
||||||
|
<th>::_("TOTAL")::</th>
|
||||||
|
<th>::roundTo(total,2)::::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary" value="::_("Update the order")::" name="submit" id="submit" />
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<h2>::c.name::</h2>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
::if c.description!=null::
|
||||||
|
::raw nl2br(c.description)::
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
::foreach p c.getProducts(true)::
|
||||||
|
<div class="col-md-4" id="product::p.id::">
|
||||||
|
<table class="product">
|
||||||
|
<tr>
|
||||||
|
<td style="vertical-align:top;">
|
||||||
|
<a onclick="_.overlay('/shop/productInfo/::p.id::','::escapeJS(p.getName())::')" >
|
||||||
|
<!--<img src="::p.getImage()::" style="width:64px;height:64px;" /> -->
|
||||||
|
<div style="background-image:url('::p.getImage()::')" class="productImg"/>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h4>
|
||||||
|
<a onclick="_.overlay('/shop/productInfo/::p.id::','::escapeJS(p.getName())::')" href="#">::p.getName()::</a>
|
||||||
|
</h4>
|
||||||
|
<span class="price">::formatNum(p.getPrice()):: ::currency()::</span>
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
::set vendor = c._vendor::
|
||||||
|
<div class="col-md-3" ::cond vendor._image!=null::>
|
||||||
|
|
||||||
|
<img src="::file(vendor._image)::" class="img-thumbnail" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
|
||||||
|
<h4>::vendor.name::</h4>
|
||||||
|
|
||||||
|
<b>::vendor.city::</b> (::vendor.zipCode::)
|
||||||
|
|
||||||
|
<p ::cond vendor.desc!=null:: style="font-size: 13px;">
|
||||||
|
::raw nl2br(vendor.desc)::
|
||||||
|
</p>
|
||||||
|
|
||||||
|
::if vendor.linkUrl!=null::
|
||||||
|
<p>
|
||||||
|
::if vendor.linkText!=null::
|
||||||
|
<a href="::vendor.linkUrl::" target="_blank" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-chevron-right"></span> ::vendor.linkText::</a>
|
||||||
|
::else::
|
||||||
|
<a href="::vendor.linkUrl::" target="_blank" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-chevron-right"></span> ::_("Read more")::</a>
|
||||||
|
::end::
|
||||||
|
</p>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<h4>::_("Coordinator")::</h4>
|
||||||
|
::if c._contact!=null::
|
||||||
|
<b>::c._contact.firstName:: ::c._contact.lastName::</b><br/>
|
||||||
|
<span ::cond c._contact.email!=null::><span class="glyphicon glyphicon-envelope"></span> <a href="mailto: ::c._contact.email::">::c._contact.email::</a><br/></span>
|
||||||
|
<span ::cond c._contact.phone!=null::><span class="glyphicon glyphicon-phone-alt"></span> ::c._contact.phone::<br/></span>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="article">
|
||||||
|
<h3>::_("Next distributions")::</h3>
|
||||||
|
|
||||||
|
::set distribs = c.getDistribs(true,null)::
|
||||||
|
<ul style="max-height:600px;overflow-y:auto;">
|
||||||
|
::foreach d distribs::
|
||||||
|
<li>
|
||||||
|
|
||||||
|
<b>::dDate(d.date)::</b>
|
||||||
|
<br/>::d._place.name::
|
||||||
|
|
||||||
|
<p ::cond d.canOrderNow()::>
|
||||||
|
::if user._amap.hasShopMode()::
|
||||||
|
<a href="/shop/::d._place.id::/::d.date.toString().substr(0,10)::" class="btn btn-primary"><span class="glyphicon glyphicon-chevron-right"></span> ::_("Order")::</a>
|
||||||
|
::end::
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
::end::
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
::if distribs.length==0::
|
||||||
|
<p>
|
||||||
|
::_("No planned distributions.")::
|
||||||
|
</p>
|
||||||
|
::else::
|
||||||
|
<p ::cond !user._amap.hasShopMode()::>
|
||||||
|
<a href="/contract/order/::c.id::" class="btn btn-primary"><span class="glyphicon glyphicon-chevron-right"></span> ::_("Order")::</a>
|
||||||
|
</p>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<p>
|
||||||
|
<a style="font-size:12px;" href='/contract'>::_("You can check your previous orders in 'my account' section")::</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<style>
|
||||||
|
.calDiv{
|
||||||
|
width:14.28%;
|
||||||
|
background:grey;
|
||||||
|
padding:2px;
|
||||||
|
height:50px;
|
||||||
|
border:solid 1px black;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<h2>::_("Calendar")::</h2>
|
||||||
|
|
||||||
|
<div class="calDiv"></div><div class="calDiv"></div><div class="calDiv"></div><div class="calDiv"></div><div class="calDiv"></div><div class="calDiv"></div><div class="calDiv"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<h2>Calendrier</h2>
|
||||||
|
|
||||||
|
<div style="-webkit-column-count: 3;-moz-column-count: 3;column-count: 3;">
|
||||||
|
|
||||||
|
::foreach c calendar::
|
||||||
|
|
||||||
|
::if c.d.getTime() < now::
|
||||||
|
::set col="0.3"::
|
||||||
|
::else::
|
||||||
|
::set col="1"::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<div style="opacity: ::col::;margin-bottom:8px;">
|
||||||
|
|
||||||
|
::hDate(c.d)::<br/>
|
||||||
|
::foreach e c.events::
|
||||||
|
<div style="margin-left:16px;margin-right:16px;width:12px;height:8px;display:inline-block;background: ::intToHex(e.color,6)::;"></div><b> ::e.name:: </b><br/>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
::set c = contract::
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="block">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/contractAdmin/products/::c.id::">::_("Products")::</a></li>
|
||||||
|
<li><a href="/contractAdmin/distributions/::c.id::">::_("Deliveries")::</a></li>
|
||||||
|
<li><a href="/contractAdmin/orders/::c.id::">::_("Orders")::</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<span class="title" style="margin-bottom: 18px;display: block;">::c.name:: - ::c._vendor.name::</span>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="span4">
|
||||||
|
<b>Produits</b>
|
||||||
|
<table class="table">
|
||||||
|
::foreach p c.getProducts()::
|
||||||
|
<tr>
|
||||||
|
<td>::p.name::</td>
|
||||||
|
<td>$$edit(modifier,/product/edit/::p.id::)</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
$$insert(Nouveau produit,/product/insert)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="span4">
|
||||||
|
<b>::_("Deliveries")::</b>
|
||||||
|
<table class="table">
|
||||||
|
::foreach d c.getDistribs()::
|
||||||
|
<tr>
|
||||||
|
<td>::d.date.toString()::</td>
|
||||||
|
<td>$$edit(modifier,/distribution/edit/::d.id::) </td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
$$insert(Nouvelle livraison,/distribution/insert)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
|
||||||
|
<!-- Distributions to validate -->
|
||||||
|
<div class="article text-center" ::cond multidistribs.length>0:: style="max-height:400px;overflow-y: auto;">
|
||||||
|
<h2>::_("Distributions to validate")::</h2>
|
||||||
|
|
||||||
|
<p>::_("You need to validate these distributions which took place recently")::</p>
|
||||||
|
|
||||||
|
::foreach m multidistribs::
|
||||||
|
<p>
|
||||||
|
::if(!m.isConfirmed())::
|
||||||
|
<a href="/distribution/validate/::m.getDate().toString().substr(0,10)::/::m.getPlace().id::" class="btn btn-primary">
|
||||||
|
::dDate(m.getDate())::, ::m.getPlace().name::
|
||||||
|
</a>
|
||||||
|
::if (isAdmin)::
|
||||||
|
<a href="/distribution/autovalidate/::m.getDate().toString().substr(0,10)::/::m.getPlace().id::" class="btn btn-danger">Admin : Autovalidate</a>
|
||||||
|
::end::
|
||||||
|
::elseif(isAdmin)::
|
||||||
|
<a class="btn btn-default disabled">
|
||||||
|
<span class="glyphicon glyphicon-ok"></span> ::dDate(m.getDate())::, ::m.getPlace().name::
|
||||||
|
</a>
|
||||||
|
<a href="/distribution/unvalidate/::m.getDate().toString().substr(0,10)::/::m.getPlace().id::" class="btn btn-danger">Admin : Unvalidate</a>
|
||||||
|
::end::
|
||||||
|
</p>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article" id="contracts">
|
||||||
|
<h2>
|
||||||
|
::if getParam("old")::
|
||||||
|
::_("Old contracts")::
|
||||||
|
::else::
|
||||||
|
::_("Contracts")::
|
||||||
|
::end::
|
||||||
|
</h2>
|
||||||
|
<table class="table">
|
||||||
|
::foreach c contracts::
|
||||||
|
::if c.endDate!=null && c.endDate.getTime() < Date.now().getTime()::
|
||||||
|
::set class = "disabled"::
|
||||||
|
::elseif c.startDate!=null && c.startDate.getTime() > Date.now().getTime()::
|
||||||
|
::set class = "disabled"::
|
||||||
|
::else::
|
||||||
|
::set class = ""::
|
||||||
|
::end::
|
||||||
|
<tr class="::class::" >
|
||||||
|
<td>
|
||||||
|
::if c._vendor._image!=null::
|
||||||
|
<div class="productImg" style="background-image:url('::file(c._vendor._image)::');width:48px;height:48px;" ></div>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>::c._vendor.name::</td>
|
||||||
|
<td>
|
||||||
|
<b>::c.name::</b>
|
||||||
|
</td>
|
||||||
|
<td style="font-size:0.9em;">
|
||||||
|
du ::hDate(c.startDate)::<br/>
|
||||||
|
au ::hDate(c.endDate)::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
$$edit(::_("Manage")::,/contractAdmin/view/::c.id::)
|
||||||
|
|
||||||
|
::if user.canManageAllContracts()::
|
||||||
|
$$delete(::_("Del.||short version of delete")::,/contract/delete/::c.id::?token=::token::)
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
::if user.canManageAllContracts()::
|
||||||
|
$$insert(::_("New contract")::,/contract/insert/)
|
||||||
|
::end::
|
||||||
|
<a href="/contractAdmin?old=1" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-folder-close"></span> ::_("Old contracts")::</a>
|
||||||
|
<a href="/contractAdmin/calendar" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-calendar"></span> ::_("Calendar")::</a>
|
||||||
|
<div class="dropdown" style="display:inline-block;">
|
||||||
|
<button class="btn btn-default btn-sm dropdown-toggle btn-noAntiDoubleClick" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||||
|
::_("Global orders view")::
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||||
|
<li><a href="/contractAdmin/ordersByDate"><span class="glyphicon glyphicon-list"></span> ::_("Same day")::</a></li>
|
||||||
|
<li><a href="/contractAdmin/ordersByTimeFrame"><span class="glyphicon glyphicon-list"></span> ::_("Timeframe")::</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="article" id="vendors">
|
||||||
|
<h2>::_("Farmers")::</h2>
|
||||||
|
<table class="table">
|
||||||
|
::foreach v vendors::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::if v._image!=null::
|
||||||
|
<div class="productImg" style="background-image:url('::file(v._image)::');width:48px;height:48px;"></div>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td><b>::v.name::</b></td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
$$edit(::_("Edit")::,/vendor/edit/::v.id::)
|
||||||
|
<a href="/vendor/addImage/::v.id::" class="btn btn-default btn-sm"> <span class="glyphicon glyphicon-picture"></span> ::_("Picture")::</a>
|
||||||
|
::if user.isAmapManager():: $$delete(,/vendor/delete/::v.id::?token=::token::) ::end::
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
$$insert(::_("New farmer")::,/vendor/insert/)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="article" id="places">
|
||||||
|
|
||||||
|
<h2>::_("Delivery places")::</h2>
|
||||||
|
<table class="table">
|
||||||
|
::foreach p places::
|
||||||
|
<tr>
|
||||||
|
<td><b>::p.name::</b></td>
|
||||||
|
<td>
|
||||||
|
$$edit(::_("Edit")::,/place/edit/::p.id::)
|
||||||
|
::if user.isAmapManager():: $$delete(::_("Del.||short version for delete")::,/place/delete/::p.id::?token=::token::) ::end::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
$$insert(::_("New delivery place")::,/place/insert/)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
<h2>::_("Distributions")::</h2>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
::foreach d deliveries::
|
||||||
|
|
||||||
|
::if d.date!=null && d.date.getTime() < Date.now().getTime()::
|
||||||
|
::set style = "opacity:0.5;"::
|
||||||
|
::else::
|
||||||
|
::set style = ""::
|
||||||
|
::end::
|
||||||
|
<tr style="::style::" >
|
||||||
|
|
||||||
|
<td>
|
||||||
|
::if d.distributionCycleId!=null::
|
||||||
|
<img src="/img/icons/arrow_refresh.png" style="opacity:0.5;" />
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::set da = getDate(d.date)::
|
||||||
|
|
||||||
|
::if d.end!=null::
|
||||||
|
|
||||||
|
::set e = getDate(d.end)::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::da.dow:: ::da.d:: ::da.m:: de ::da.h+":"+da.i::
|
||||||
|
|
||||||
|
::if d.end!=null::
|
||||||
|
à ::e.h+":"+e.i::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::d._place.name::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group">
|
||||||
|
$$edit(::_("Edit")::,/distribution/edit/::d.id::)
|
||||||
|
$$delete(::_("Delete")::,/distribution/delete/::d.id::)
|
||||||
|
<a href="/distribution/list/::d.id::" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-print"></span> ::_("Distribution list")::</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<table ::cond cycles.length>0:: class="table">
|
||||||
|
<tr>
|
||||||
|
<th>::_("Distribution cycle")::</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
::foreach c cycles::
|
||||||
|
<tr>
|
||||||
|
<td><b>
|
||||||
|
::switch c.cycleType::
|
||||||
|
::case:: ::_("Weekly")::
|
||||||
|
::case:: ::_("Monthly")::
|
||||||
|
::case:: ::_("Every 2 weeks")::
|
||||||
|
::case:: ::_("Every 3 weeks")::
|
||||||
|
::end::
|
||||||
|
</b>
|
||||||
|
: Du ::hDate(c.startDate):: au ::hDate(c.endDate)::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
$$delete(::_("Del.")::,::"/distribution/deleteCycle/"+c.id::)
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
$$insert(::_("One time distribution")::,/distribution/insert/::c.id::)
|
||||||
|
$$insert(::_("Distribution cycle")::,/distribution/insertCycle/::c.id::)
|
||||||
|
|
||||||
|
<a href="/contractAdmin/distributionp/::c.id::" class="btn btn-default btn-sm">
|
||||||
|
<span class="glyphicon glyphicon-user"></span> ::_("Distributions engagement")::
|
||||||
|
</a>
|
||||||
|
<a href="/contractAdmin/distributions/::c.id::?old=1" class="btn btn-default btn-sm">
|
||||||
|
::_("Old distributions")::
|
||||||
|
</a>
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h2>::c.name:: - ::c._vendor.name::</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="article">
|
||||||
|
<ul id="subnav" class="nav nav-pills nav-stacked">
|
||||||
|
<li id="default" ::attr class if(nav[1]=="view") 'active'::>
|
||||||
|
<a href="/contractAdmin/view/::c.id::">::_("Summary")::</a></li>
|
||||||
|
<li id="edit">
|
||||||
|
<a href="/contract/edit/::c.id::">::_("Edit contract")::</a></li>
|
||||||
|
<li id="products" ::attr class if(nav[1]=="products") 'active'::>
|
||||||
|
<a href="/contractAdmin/products/::c.id::">::_("Products")::</a></li>
|
||||||
|
<li id="deliveries" ::attr class if(nav[1]=="distributions") 'active'::>
|
||||||
|
<a href="/contractAdmin/distributions/::c.id::">::_("Distributions")::</a></li>
|
||||||
|
<li id="orders" ::attr class if(nav[1]=="orders") 'active'::>
|
||||||
|
<a href="/contractAdmin/orders/::c.id::">::_("Orders")::</a></li>
|
||||||
|
<li id="stats">
|
||||||
|
<a href="/contractAdmin/stats/::c.id::">::_("Stats")::</a></li>
|
||||||
|
::if navbar!=null::
|
||||||
|
<hr/>
|
||||||
|
::foreach i navbar::
|
||||||
|
<li ::attr class if(nav[1]==i.id) 'active'::>
|
||||||
|
<a href="::i.link::">
|
||||||
|
<span ::cond i.icon:: class="glyphicon glyphicon-::i.icon::" ></span>
|
||||||
|
::i.name::</a>
|
||||||
|
</li>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::end::
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="article">
|
||||||
|
::raw __content__::
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
<h2>::_("Participate to deliveries")::</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
::_("This page allows to see if members have participated to deliveries enough.")::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin:16px 0 16px 0;text-align:center;" class="alert-warning">
|
||||||
|
<strong>::raw __("::distribs:: deliveries requiring ::distributorNum:: person(s)",{distribs:distribs,distributorNum:distributorNum})::,<br/>
|
||||||
|
::users:: ::_("person(s) using this contract"):: <br/>
|
||||||
|
= ::num:: ::_("participations to deliveries to do per family.")::</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th class="col-md-5">::_("Suscriber")::</th>
|
||||||
|
<th class="col-md-5">::raw _("Number of attendances to deliveries")::</th>
|
||||||
|
<th class="col-md-2"></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::foreach p participations::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::p.user.getCoupleName()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::p.count::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if p.count>=numRounded::
|
||||||
|
<img src="/img/icons/accept.png" /> OK
|
||||||
|
::else::
|
||||||
|
<img src="/img/icons/stop.png" /> ::_("Insufficient")::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
::if(extParticipations.length>0)::
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th class="col-md-5">::_("Member without order in this contract")::</th>
|
||||||
|
<th class="col-md-5">::raw _("Number of attendances to deliveries")::</th>
|
||||||
|
<th class="col-md-2"></th>
|
||||||
|
</tr>
|
||||||
|
::foreach p extParticipations::
|
||||||
|
<tr>
|
||||||
|
<td>::p.user.getCoupleName()::</td>
|
||||||
|
<td>::p.count::</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
|
||||||
|
::if u==null::
|
||||||
|
<h3>::_("Key-in an order")::</h3>
|
||||||
|
::else::
|
||||||
|
<h3>::_("Key-in an order for"):: ::u.getCoupleName()::</h3>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
<p>::_("For the delivery of the"):: <b>::hDate(distribution.date)::</b></p>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<form name="order" id="order" method="POST" action="" class="form-horizontal">
|
||||||
|
<input type="hidden" name="token" value="::token::"/>
|
||||||
|
::if distribution!=null::
|
||||||
|
<input type="hidden" name="distribution" value="::distribution.id::" />
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if u==null ::
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="user" class="control-label col-md-2 text-left">::_("Member:")::</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<select name="user" id="user" class="form-control">
|
||||||
|
::foreach uu users::
|
||||||
|
<option value="::uu.value::">::uu.label::</option>
|
||||||
|
::end::
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Price")::</th>
|
||||||
|
<th>::_("Quantity")::</th>
|
||||||
|
<th ::cond !user._amap.hasPayments()::>::_("Status")::</th>
|
||||||
|
::if distribution==null::<th>::_("Alternately with :")::</th>::end::
|
||||||
|
</tr>
|
||||||
|
::set total = 0::
|
||||||
|
::foreach uo userOrders::
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::if uo.product.active==false::
|
||||||
|
<img src="::uo.product.getImage()::" style="width:32px;height:32px;opacity:0.3;" />
|
||||||
|
::else::
|
||||||
|
<img src="::uo.product.getImage()::" style="width:32px;height:32px;" />
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if uo.product.active==false::
|
||||||
|
<span style="color:#AAA;">::uo.product.getName():: ::_("(inactive)")::</span>
|
||||||
|
::else::
|
||||||
|
::uo.product.getName()::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(uo.product.price):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if uo.order==null::
|
||||||
|
::set q = 0::
|
||||||
|
::else::
|
||||||
|
::set q = uo.order.quantity::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::set total = total+(q*uo.product.price)::
|
||||||
|
<input type="text" class="form-control input-sm" name="product::uo.product.id::" id="product::uo.product.id::" value="::q::" />
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- "paid" checkbox only if payments are not enabled -->
|
||||||
|
<td class="paid" ::cond !user._amap.hasPayments()::>
|
||||||
|
::_("Paid:")::
|
||||||
|
::if uo.order!=null::
|
||||||
|
<input type="checkbox" name="paid::uo.product.id::" value="1" ::attr checked (uo.order.paid==true ):: />
|
||||||
|
::else::
|
||||||
|
<input type="checkbox" name="paid::uo.product.id::" value="1" />
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td ::cond distribution==null::>
|
||||||
|
|
||||||
|
<select name="user2::uo.product.id::" class="form-control input-sm" style="width:150px;display:inline-block;">
|
||||||
|
<option value="0">-</option>
|
||||||
|
::foreach uu users::
|
||||||
|
<option value="::uu.value::" ::attr selected (uo.order!=null && uo.order._user2!=null && uo.order._user2.id==uu.value)::>::uu.label::</option>
|
||||||
|
::end::
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input data-toggle="tooltip" title="::_("Reverse the alternation")::" type="checkbox" name="invert::uo.product.id::" value="1" ::attr checked (uo.order!=null && uo.order.hasInvertSharedOrder()==true ):: />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::set extra = 0::
|
||||||
|
|
||||||
|
::if c.hasPercentageOnOrders()::
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>::_("Total order")::</th>
|
||||||
|
<th>::total::::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<td>::c.percentageName:: (::c.percentageValue::%)</td>
|
||||||
|
::set extra = total*(c.percentageValue/100)::
|
||||||
|
<td>::extra::::currency()::</td>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::total+extra::::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
<th ::cond !user._amap.hasPayments()::>
|
||||||
|
<a href="#" id="checkButton">::_("Check all")::</a>
|
||||||
|
<script language="javascript">
|
||||||
|
var globalCheck = true;
|
||||||
|
var checkEverything = function(e){
|
||||||
|
var boxes = $("td.paid :checkbox");
|
||||||
|
boxes.attr("checked",globalCheck);
|
||||||
|
globalCheck=!globalCheck;
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
$("#checkButton").click(checkEverything);
|
||||||
|
</script>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary" value="::_("Validate the order")::" name="submit" id="submit" />
|
||||||
|
|
||||||
|
</form>
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
<h3>::_("Orders")::</h3>
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
::_("Delivery of the"):: <b>::hDate(distribution.date)::</b>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<div id="orderBox"></div><!-- for react -->
|
||||||
|
|
||||||
|
<div style="margin:8px 0;">
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
<a href="/contractAdmin/edit/::c.id::?d=::distribution.id::" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-plus-sign"></span> ::_("New order")::</a>
|
||||||
|
::else::
|
||||||
|
<a href="/contractAdmin/edit/::c.id::" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-plus-sign"></span> ::_("New order")::</a>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
$$export(/contractAdmin/orders/::c.id::?csv=1&d=::distribution.id::)
|
||||||
|
<a href="/contractAdmin/ordersByProduct/::c.id::?d=::distribution.id::" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-list"></span>
|
||||||
|
::_("Summary by products")::
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="dropdown" style="display:inline-block;">
|
||||||
|
<button class="btn btn-default btn-sm dropdown-toggle btn-noAntiDoubleClick" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="glyphicon glyphicon-print"></span> ::_("Attendance sheet")::
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||||
|
<li><a href="/distribution/list/::distribution.id::"> ::_("Standard")::</a></li>
|
||||||
|
<li><a href="/distribution/listByProductUser/::distribution.id::"> ::_("By Product-User")::</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::else::
|
||||||
|
$$export(/contractAdmin/orders/::c.id::?csv=1)
|
||||||
|
<a href="/contractAdmin/ordersByProduct/::c.id::" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-list"></span>
|
||||||
|
::_("Summary by products")::</a>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::if disabledProducts > 0::
|
||||||
|
$$alert( ::raw __("Be careful, there are <b>::disabledProducts::</b> order(s) containing <b>disabled products</b>.",{disabledProducts:disabledProducts}):: )
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td.userRow{
|
||||||
|
font-size:120%;
|
||||||
|
color:#e7e7e7;
|
||||||
|
}
|
||||||
|
td.userRow a{ color:white; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
::set total = 0::
|
||||||
|
::set totalAdh = 0::
|
||||||
|
<table class="table table-bordered table-hover table-condensed" style="width:100%;">
|
||||||
|
::set name = ""::
|
||||||
|
::set name2 = ""::
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
<tr style="background-color:#666;">
|
||||||
|
|
||||||
|
<td colspan="7" style="font-size:120%;color:#e7e7e7;">
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
::set basket = getBasket(m.userId,distribution._place.id,distribution.date)::
|
||||||
|
<div class="basketNumber" ::cond basket!=null:: >
|
||||||
|
<i class="fa fa-shopping-basket" aria-hidden="true"></i> N°::basket.num::
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::m.userName::
|
||||||
|
::if m.userName2!=null::
|
||||||
|
<span class="glyphicon glyphicon-refresh"></span> ::_("alternated with"):: ::m.userName2::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::set name = m.userName ::
|
||||||
|
::set name2 = m.userName2 ::
|
||||||
|
::set totalAdh = totalAdh+1::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!--
|
||||||
|
::if distribution!=null::
|
||||||
|
<a href="/contractAdmin/edit/::c.id::/::m.userId::?d=::distribution.id::" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-edit"></span>
|
||||||
|
::_("Edit")::</a>
|
||||||
|
::else::
|
||||||
|
<a href="/contractAdmin/edit/::c.id::/::m.userId::" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-edit"></span>
|
||||||
|
::_("Edit")::</a>
|
||||||
|
::end::
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
<a class="btn btn-default btn-xs"
|
||||||
|
onclick="_.initOrderBox(::m.userId::,::distribution.id::,::distribution._contract.id::,::distribution._contract.type::,'::escapeJS(hDate(distribution.date))::','::escapeJS(distribution._place.toString())::','::escapeJS(m.userName)::','::user.getAmap().currency::',::user.getAmap().hasPayments()::,'/contractAdmin/orders/::c.id::?d=::distribution.id::')">
|
||||||
|
<span class="glyphicon glyphicon-edit"></span>
|
||||||
|
::_("Edit")::
|
||||||
|
</a>
|
||||||
|
::else::
|
||||||
|
<a class="btn btn-default btn-xs"
|
||||||
|
onclick="_.initOrderBox(::m.userId::, null, ::c.id::,::c.type::, null, null, '::escapeJS(m.userName)::','::user.getAmap().currency::',::user.getAmap().hasPayments()::,'/contractAdmin/orders/::c.id::')">
|
||||||
|
<span class="glyphicon glyphicon-edit"></span>
|
||||||
|
::_("Edit")::
|
||||||
|
</a>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>::_("Prod. ID")::</th>
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th><a href="#" data-toggle="tooltip" data-placement="top" title="::_("Unit price including taxes")::">::_("U.P||price per unit")::</a></th>
|
||||||
|
<th>::_("Sub-total")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::_("Paid")::</th>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
::if m.disabled==true::
|
||||||
|
::set class="danger"::
|
||||||
|
::else::
|
||||||
|
::set class=""::
|
||||||
|
::end::
|
||||||
|
<tr class="::class::">
|
||||||
|
<td class="ref">
|
||||||
|
$$nullSafe(::m.productRef::)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if(m.quantity==0 && m.canceled)::
|
||||||
|
<span style="color:#AAA">::_("Canceled")::</span>
|
||||||
|
::else::
|
||||||
|
::raw m.smartQt::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
::m.productName::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.subTotal):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- frais -->
|
||||||
|
::if m.percentageValue!=null::
|
||||||
|
<a href="#" data-toggle="tooltip" data-placement="top" title="::m.percentageName:: : ::m.percentageValue:: %">
|
||||||
|
::formatNum(m.fees):: ::currency()::
|
||||||
|
</a>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- total -->
|
||||||
|
::formatNum(m.total):: ::currency()::
|
||||||
|
::set total = total + m.total::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if !m.canceled && m.quantity!=0::
|
||||||
|
::if m.paid==true::
|
||||||
|
<span style="color:#00AA00;">::_("Paid")::</span>
|
||||||
|
::else::
|
||||||
|
<span style="color:#DD0000;">::_("Not paid")::</span>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if(m.quantity==0)::
|
||||||
|
|
||||||
|
::if distribution==null::
|
||||||
|
<a href="/contractAdmin/orders/::c.id::?delete=::m.id::&token=::token::" $$confirm(::__("Do you really want to delete this order row for the product \"::productName::\" ?",{productName:m.productName})::) class="btn btn-danger btn-xs" >
|
||||||
|
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
||||||
|
::_("Delete")::
|
||||||
|
</a>
|
||||||
|
::else::
|
||||||
|
<a href="/contractAdmin/orders/::c.id::?d=::distribution.id::&delete=::m.id::&token=::token::" $$confirm(::__("Do you really want to delete this order row for the product \"::productName::\" ?",{productName:m.productName})::) class="btn btn-danger btn-xs" >
|
||||||
|
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
||||||
|
::_("Delete")::
|
||||||
|
</a>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<tr style="background:#DDD;">
|
||||||
|
|
||||||
|
<th colspan="6">::_("Total")::</th>
|
||||||
|
<th>::formatNum(total):: ::currency()::</th>
|
||||||
|
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<div style="margin-top:18px;margin-bottom:18px;">
|
||||||
|
::raw __("<b>::totalAdh::</b> member(s) in this distribution.",{totalAdh:totalAdh})::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<h3>
|
||||||
|
Distribution du ::hDate(date)::
|
||||||
|
</h3>
|
||||||
|
<h4><span class="glyphicon glyphicon-map-marker"></span>::place::</h4>
|
||||||
|
<br/>
|
||||||
|
<!--::if(confirmed)::
|
||||||
|
::_("Validated")::
|
||||||
|
::else::
|
||||||
|
::_("Not validated")::
|
||||||
|
::end::-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="/distribution/listByDate/::date.toString().substr(0,10)::/::place.id::/csv" class="btn btn-default btn-sm"> <span class="glyphicon glyphicon-list-alt"></span> ::_("CSV Export"):: </a>
|
||||||
|
<a href="/distribution/listByDate/::date.toString().substr(0,10)::/::place.id::" class="btn btn-default btn-sm"> <span class="glyphicon glyphicon-print"></span> ::_("Attendance sheet"):: </a>
|
||||||
|
|
||||||
|
::if ctotal::
|
||||||
|
<a href="/contractAdmin/ordersByDate/::date.toString().substr(0,10)::/::place.id::" class="btn btn-default btn-sm">::_("Totals per member")::</a>
|
||||||
|
::else::
|
||||||
|
<a href="/contractAdmin/ordersByDate/::date.toString().substr(0,10)::/::place.id::?ctotal=1" class="btn btn-default btn-sm">::_("Sub-totals per contract")::</a>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<a href="/contractAdmin/vendorsByDate/::date.toString().substr(0,10)::/::place.id::" class="btn btn-default btn-sm">::_("Totals per supplier")::</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-hover table-condensed" style="width:100%;">
|
||||||
|
|
||||||
|
::set total = 0:: <!-- big total -->
|
||||||
|
::set subtotal = 0:: <!-- total per user -->
|
||||||
|
::set contractTotal = 0:: <!-- total per contract -->
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
|
||||||
|
::set i = repeat.m.index::
|
||||||
|
|
||||||
|
<!-- name change -->
|
||||||
|
::if orders[i-1]==null || m.userName != orders[i-1].userName || m.userName2 != orders[i-1].userName2::
|
||||||
|
|
||||||
|
<tr style="background-color:#666;">
|
||||||
|
<td colspan="9" style="font-size:120%;color:#e7e7e7;">
|
||||||
|
|
||||||
|
::set basket = getBasket(m.userId,place.id,date)::
|
||||||
|
|
||||||
|
<div class="basketNumber" ::cond basket!=null:: >
|
||||||
|
<i class="fa fa-shopping-basket" aria-hidden="true"></i> ::_("Num"):: ::basket.num::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::m.userName::
|
||||||
|
::if m.userName2!=null::
|
||||||
|
<span class="glyphicon glyphicon-refresh"></span> ::_("alternately with"):: ::m.userName2::
|
||||||
|
::end::
|
||||||
|
::set contractTotal = 0::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>::_("Contract")::</th>
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
<th>::_("Ref")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th><a href="#" data-toggle="tooltip" data-placement="top" title="::_("Unit price incl. VAT")::">::_("U.P.")::</a></th>
|
||||||
|
<th>::_("Sub-total")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::_("Paid")::</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- 1 order line -->
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/contractAdmin/view/::m.contractId::">
|
||||||
|
::short(m.contractName,40)::
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if(m.quantity==0 && m.canceled)::
|
||||||
|
<span style="color:#AAA">::_("Canceled")::</span>
|
||||||
|
::else::
|
||||||
|
::raw m.smartQt::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td class="ref">
|
||||||
|
$$nullSafe(::m.productRef::)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::short(m.productName,40)::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.subTotal):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- fees -->
|
||||||
|
::if m.percentageValue!=null::
|
||||||
|
<a href="#" data-toggle="tooltip" data-placement="top" title="::m.percentageName:: : ::m.percentageValue:: %">
|
||||||
|
::formatNum(m.fees):: ::currency()::
|
||||||
|
</a>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- total -->
|
||||||
|
::formatNum(m.total):: ::currency()::
|
||||||
|
::set total = total + m.total::
|
||||||
|
::set subtotal = subtotal + m.total::
|
||||||
|
::set contractTotal = contractTotal + m.total::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.paid==true::
|
||||||
|
<span style="color:#00AA00;">::_("Paid")::</span>
|
||||||
|
::else::
|
||||||
|
<span style="color:#DD0000;">::_("Not paid")::</span>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- contract total -->
|
||||||
|
::if ctotal && (orders[i+1]==null || m.contractName != orders[i+1].contractName || m.userName != orders[i+1].userName )::
|
||||||
|
|
||||||
|
<tr style="background:#DDD;">
|
||||||
|
<th colspan="7" class="text-right">Total ::m.contractName::</th>
|
||||||
|
<th>::formatNum(contractTotal):: ::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
::set contractTotal = 0::
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- member total -->
|
||||||
|
::if (orders[i+1]==null || m.userName != orders[i+1].userName) ::
|
||||||
|
|
||||||
|
::if subtotal!=0::
|
||||||
|
<tr style="border-top:2px solid #AAA;">
|
||||||
|
<th colspan="7" class="text-right">::_("Total member")::</th>
|
||||||
|
<th>::formatNum(subtotal):: ::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
::set subtotal = 0::
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- end orders loop -->
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- big total -->
|
||||||
|
<tr style="background:#CCC;font-size:1.3em;">
|
||||||
|
<th colspan="7" class="text-right">::_("Total of all orders:"):: </th>
|
||||||
|
<th>::formatNum(total):: ::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
<h3>::_("Summary table per product")::</h3>
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
<p>
|
||||||
|
::set date = hDate(distribution.date)::
|
||||||
|
::raw __("Delivery of the <b>::date::</b>",{date:date})::
|
||||||
|
</p>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<p>
|
||||||
|
::_("Be careful, if the price of the product was modified during the sale, every order of a member is taken into account with the price as it was at the time of the creation of the order")::
|
||||||
|
</p>
|
||||||
|
::set total = 0::
|
||||||
|
<table class="table table-bordered table-hover" style="width:100%;">
|
||||||
|
<tr>
|
||||||
|
<th>::_("Weight/Vol.")::</th>
|
||||||
|
<th>::_("Quantities")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Reference")::</th>
|
||||||
|
<th>::_("Unit Price incl. VAT")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::raw m.weightOrVolume::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.quantity)::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::m.pname::
|
||||||
|
</td>
|
||||||
|
<td class="ref">
|
||||||
|
$$nullSafe(::m.ref::)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.priceTTC):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.totalTTC):: ::currency()::
|
||||||
|
::set total = total + m.totalTTC::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<tr style="background:#DDD;">
|
||||||
|
<th colspan="5">Total</th>
|
||||||
|
<th>::formatNum(total):: ::currency()::</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
::if distribution!=null::
|
||||||
|
$$export(/contractAdmin/ordersByProduct/::c.id::?csv=1&d=::distribution.id::)
|
||||||
|
<a href="/contractAdmin/ordersByProductList/::c.id::?d=::distribution.id::" class="btn btn-default btn-sm">
|
||||||
|
<span class="glyphicon glyphicon-list"></span> ::_("Order form")::</a>
|
||||||
|
::else::
|
||||||
|
$$export(/contractAdmin/ordersByProduct/::c.id::?csv=1)
|
||||||
|
<a href="/contractAdmin/ordersByProductList/::c.id::" class="btn btn-default btn-sm">
|
||||||
|
<span class="glyphicon glyphicon-list"></span> ::_("Order form")::</a>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Bon de commande</title>
|
||||||
|
<link href="/css/print.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="table table-bordered table-hover" style="width:100%;">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::if group._image!=null::
|
||||||
|
<img src="::file(group._image)::" style="margin:auto;height:150px;" class="thumbnail" />
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<h1>::group.name::</h1>
|
||||||
|
::if distribution!=null::
|
||||||
|
<h2>Livraison du <b>::hDate(distribution.date)::</b></h2>
|
||||||
|
::end::
|
||||||
|
<h2>::c.name:: - ::c._vendor.name::</h2>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::set cv = c._vendor::
|
||||||
|
::if cv._image!=null::
|
||||||
|
<img src="::file(cv._image)::" class="thumbnail" style="margin:auto;height:150px;" />
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
::set cc = c._contact::
|
||||||
|
::set cv = c._vendor::
|
||||||
|
<b>Responsable contrat : </b> ::cc.firstName:: ::cc.lastName::, ::cc.email::,
|
||||||
|
::if cc.phone!=null :: ::cc.phone:: ::end::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Producteur : </b>::cv.name:: ,
|
||||||
|
::if cv.email!=null :: ::cv.email:: ::end::,
|
||||||
|
::if cv.phone!=null :: ::cv.phone:: ::end::
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<h3>::_("Summary table per product")::</h3>
|
||||||
|
::_("Be careful, if the price of the product was modified during the sale, every order of a member is taken into account with the price as it was at the time of the creation of the order")::
|
||||||
|
</p>
|
||||||
|
::set total = 0::
|
||||||
|
<table class="table table-bordered table-hover" style="width:100%;">
|
||||||
|
<tr style="border-top: 2px solid #AAA;">
|
||||||
|
<th>::_("Weight/Vol.")::</th>
|
||||||
|
<th>::_("Quantities")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Reference")::</th>
|
||||||
|
<th>::_("Unit price incl. VAT")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
</tr>
|
||||||
|
::foreach m orders::
|
||||||
|
<tr style="border-top: 2px solid #AAA;">
|
||||||
|
<td>
|
||||||
|
::raw m.weightOrVolume::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.quantity)::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::m.pname::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
$$nullSafe(::m.ref::)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.priceTTC):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.totalTTC):: ::currency()::
|
||||||
|
::set total = total + m.totalTTC::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<tr style="background:#DDD;">
|
||||||
|
<th colspan="5">::_("Total")::</th>
|
||||||
|
<th>::formatNum(total):: ::currency()::</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p class="hidden"><i>::_("Push \"Control + P\" to print this page.")::</i></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<h3>::_("Orders from the"):: ::hDate(from):: ::_("to"):: ::hDate(to)::</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::set uri = from.toString().substr(0,10)+"/"+to.toString().substr(0,10)::
|
||||||
|
<div class="text-center">
|
||||||
|
<!--<a href="/distribution/listByDate//csv" class="btn btn-default btn-sm"> <span class="glyphicon glyphicon-list-alt"></span> ::_("CSV Export"):: </a>-->
|
||||||
|
<a href="/contractAdmin/vendorsByTimeFrame/::uri::" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-list"></span> ::_("Totals per supplier")::</a>
|
||||||
|
<a href="/contractAdmin/ordersByTimeFrame/::uri::?ctotal=1" class="btn btn-default btn-sm">::_("Sub-totals per contract")::</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-hover table-condensed" style="width:100%;">
|
||||||
|
|
||||||
|
::set total = 0:: <!-- big total -->
|
||||||
|
::set subtotal = 0:: <!-- total per user -->
|
||||||
|
::set contractTotal = 0:: <!-- total per contract -->
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
|
||||||
|
::set i = repeat.m.index::
|
||||||
|
|
||||||
|
<!-- name change -->
|
||||||
|
::if orders[i-1]==null || m.userName != orders[i-1].userName || m.userName2 != orders[i-1].userName2::
|
||||||
|
|
||||||
|
<tr style="background-color:#666;">
|
||||||
|
<td colspan="9" style="font-size:120%;color:#e7e7e7;">
|
||||||
|
::m.userName::
|
||||||
|
::if m.userName2!=null::
|
||||||
|
<span class="glyphicon glyphicon-refresh"></span> en alternance avec ::m.userName2::
|
||||||
|
::end::
|
||||||
|
::set contractTotal = 0::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>::_("Contract")::</th>
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
<th>::_("Ref")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th><a href="#" data-toggle="tooltip" data-placement="top" title="::_("Unit price incl. VAT")::">::_("U.P.")::</a></th>
|
||||||
|
<th>::_("Sub-total")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::_("Paid")::</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- 1 order line -->
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/contractAdmin/view/::m.contractId::">
|
||||||
|
::short(m.contractName,40)::
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if(m.quantity==0 && m.canceled)::
|
||||||
|
::_("Canceled")::
|
||||||
|
::else::
|
||||||
|
::raw m.smartQt::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
$$nullSafe(::m.productRef::)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::short(m.productName,40)::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.subTotal):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- fees -->
|
||||||
|
::if m.percentageValue!=null::
|
||||||
|
<a href="#" data-toggle="tooltip" data-placement="top" title="::m.percentageName:: : ::m.percentageValue:: %">
|
||||||
|
::formatNum(m.fees):: ::currency()::
|
||||||
|
</a>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- total -->
|
||||||
|
::formatNum(m.total):: ::currency()::
|
||||||
|
::set total = total + m.total::
|
||||||
|
::set subtotal = subtotal + m.total::
|
||||||
|
::set contractTotal = contractTotal + m.total::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.paid==true::
|
||||||
|
<span style="color:#00AA00;">::_("Paid")::</span>
|
||||||
|
::else::
|
||||||
|
<span style="color:#DD0000;">::_("Not paid")::</span>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- contract total -->
|
||||||
|
::if ctotal && (orders[i+1]==null || m.contractName != orders[i+1].contractName || m.userName != orders[i+1].userName )::
|
||||||
|
|
||||||
|
<tr style="background:#DDD;">
|
||||||
|
<th colspan="7" class="text-right">Total ::m.contractName::</th>
|
||||||
|
<th>::formatNum(contractTotal):: ::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
::set contractTotal = 0::
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- member total -->
|
||||||
|
::if (orders[i+1]==null || m.userName != orders[i+1].userName) ::
|
||||||
|
|
||||||
|
::if subtotal!=0::
|
||||||
|
<tr style="border-top:2px solid #AAA;">
|
||||||
|
<th colspan="7" class="text-right">::_("Total member")::</th>
|
||||||
|
<th>::formatNum(subtotal):: ::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
::set subtotal = 0::
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- end orders loop -->
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- big total -->
|
||||||
|
<tr style="background:#CCC;font-size:1.3em;">
|
||||||
|
<th colspan="7" class="text-right">::_("Total of all orders:"):: </th>
|
||||||
|
<th>::formatNum(total):: ::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
<h3>::_("Products")::</h3>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
$$insert(::_("New product")::,/product/insert/::c.id::)
|
||||||
|
<a href="/product/import/::c.id::" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-list-alt"></span> ::_("Import from Excel/CSV")::</a>
|
||||||
|
|
||||||
|
::if !c._amap.hasTaxonomy()::
|
||||||
|
<a ::cond c.type==1:: href="/product/categorize/::c.id::" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-tag"></span> ::_("Categories")::</a>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
//enable checked products
|
||||||
|
var enableProducts = function(){
|
||||||
|
var ids = _.getCheckboxesId('form.products');
|
||||||
|
if(ids.length==0){
|
||||||
|
alert("::_("You need to select at least one product to perform this action")::");
|
||||||
|
}else{
|
||||||
|
var l = document.location;
|
||||||
|
l.href = l.protocol +"//"+ l.host + l.pathname + "?enable="+ ids.join("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//disable checked products
|
||||||
|
var disableProducts = function(){
|
||||||
|
var ids = _.getCheckboxesId('form.products');
|
||||||
|
if(ids.length==0){
|
||||||
|
alert("::_("You need to select at least one product to perform this action")::");
|
||||||
|
}else{
|
||||||
|
var l = document.location;
|
||||||
|
l.href = l.protocol +"//"+ l.host + l.pathname + "?disable="+ ids.join("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//check input when click row
|
||||||
|
/*var clickRow = function(x){
|
||||||
|
x.children[0].children[0].checked = !x.children[0].children[0].checked;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
//check all
|
||||||
|
var check = function(){
|
||||||
|
var inputs = document.querySelectorAll('form.products input');
|
||||||
|
var value = inputs[0].checked==true;
|
||||||
|
for ( i of inputs ) {
|
||||||
|
i.checked = !value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form method="GET" class="products">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th>::_("Product ID||id or reference of a product")::</th>
|
||||||
|
<th>::_("Name")::</th>
|
||||||
|
<th>::_("Price")::</th>
|
||||||
|
<th></th>
|
||||||
|
::if c.hasStockManagement()::
|
||||||
|
<th>::_("Stock")::</th>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
::foreach p c.getProducts(false)::
|
||||||
|
|
||||||
|
::if p.active==false::
|
||||||
|
::set opacity=0.4::
|
||||||
|
::else::
|
||||||
|
::set opacity=1::
|
||||||
|
::end::
|
||||||
|
<tr style="opacity: ::opacity::" onclick="/*clickRow(this)*/">
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="checkbox" value="::p.id::" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img src="::p.getImage()::" style="width:64px;height:64px;" />
|
||||||
|
</td>
|
||||||
|
<td class="ref">$$nullSafe(::p.ref::)</td>
|
||||||
|
<td>
|
||||||
|
<b>::p.name::</b>
|
||||||
|
|
||||||
|
::if p._unitType!=null && p.qt!=null::
|
||||||
|
::p.qt:: ::unit(p._unitType,false)::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if !c._amap.hasTaxonomy()::
|
||||||
|
<div>
|
||||||
|
::foreach c p.getCategories()::
|
||||||
|
<span class='tag' style='background-color: ::c.getColor()::;'>::c.name::</span>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
::else::
|
||||||
|
<div class="detail">
|
||||||
|
::p.getFullCategorization().join(" / ")::
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
<div>
|
||||||
|
::if p.organic ::<img src="/img/AB.png" style="display:inline-block"/>::end::
|
||||||
|
::if p.variablePrice ::<img src="/img/weight.png" style="display:inline-block"/>::end::
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>::formatNum(p.price):: ::currency()::</td>
|
||||||
|
<td>
|
||||||
|
::if c.hasStockManagement() ::
|
||||||
|
::if p.stock==null::
|
||||||
|
-
|
||||||
|
::else::
|
||||||
|
::if p.stock <= 10::
|
||||||
|
<span style="color:red;font-weight:bold;">
|
||||||
|
::p.stock::
|
||||||
|
</span>
|
||||||
|
::else::
|
||||||
|
::p.stock::
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
$$edit(::_("Edit")::,/product/edit/::p.id::)
|
||||||
|
<a href="/product/addImage/::p.id::" class="btn btn-default btn-sm"> <span class="glyphicon glyphicon-picture"></span> ::_("Picture")::</a>
|
||||||
|
$$delete(::_("Del.")::,/product/delete/::p.id::?token=::token::)
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<a onclick="check();" class="btn btn-default btn-sm btn-noAntiDoubleClick"> ::_("Check all")::</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<a class="btn btn-default btn-sm" onclick="enableProducts()"><span class="glyphicon glyphicon-check"></span> ::_("Enable")::</a>
|
||||||
|
<a class="btn btn-default btn-sm" onclick="disableProducts()"><span class="glyphicon glyphicon-unchecked"></span> ::_("Disable")::</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
::if distributions.length>0::
|
||||||
|
|
||||||
|
<h3>::_("Orders")::</h3>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-hover table-striped">
|
||||||
|
<tr>
|
||||||
|
<th>::_("Date")::</th>
|
||||||
|
<th>::_("Location")::</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
::foreach d distributions::
|
||||||
|
|
||||||
|
::if d.date!=null && d.date.getTime() < Date.now().getTime()::
|
||||||
|
::set style = "opacity:0.5;"::
|
||||||
|
::else::
|
||||||
|
::set style = ""::
|
||||||
|
::end::
|
||||||
|
<tr style="::style::" >
|
||||||
|
|
||||||
|
<td>::hDate(d.date)::</td>
|
||||||
|
<td>::d._place.name::</td>
|
||||||
|
<td>
|
||||||
|
<a href="/contractAdmin/orders/::c.id::?d=::d.id::" class="btn btn-default">
|
||||||
|
<span class="glyphicon glyphicon-list"></span>
|
||||||
|
::_("Orders")::
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
::else::
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
|
||||||
|
<b>Aucune distribution prochainement.</b><br/>
|
||||||
|
<a href="/contractAdmin/distributions/::c.id::">::_("Click here to manage deliveries")::</a>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<a href="?old=1" class="btn btn-default btn-sm">
|
||||||
|
::_("Old orders")::
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="/contractAdmin/stats/::c.id::/?stat=0">
|
||||||
|
::_("Seniority of subscribers")::
|
||||||
|
</a> |
|
||||||
|
<a href="/contractAdmin/stats/::c.id::/?stat=1">
|
||||||
|
::_("Sharing between orders")::
|
||||||
|
</a><br/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::if stat==0::
|
||||||
|
<h3>
|
||||||
|
::__("Seniority of subscribers of ::contract::",{contract:c.name})::
|
||||||
|
|
||||||
|
</h3>
|
||||||
|
<table class="table" style="width:50%;">
|
||||||
|
<tr>
|
||||||
|
<th>::_("Number")::</th>
|
||||||
|
<th>::_("Year")::</th>
|
||||||
|
</tr>
|
||||||
|
::foreach a anciennete::
|
||||||
|
<tr>
|
||||||
|
<td>::a.cnt::</td>
|
||||||
|
<td>::a.uyear::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if stat==1::
|
||||||
|
<h3>::_("Sharing between orders of"):: "::c.name::"</h3>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>::_("Quantity")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Total price")::</th>
|
||||||
|
<th>::_("% of orders")::</th>
|
||||||
|
</tr>
|
||||||
|
::foreach r repartition::
|
||||||
|
<tr>
|
||||||
|
<td>::r.quantity::</td>
|
||||||
|
<td>::r.name::</td>
|
||||||
|
<td>::r.quantity*r.price:: ::currency()::</td>
|
||||||
|
<td>::r.percent:: %</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Total: ::totalQuantity:: ::_("products")::</th>
|
||||||
|
|
||||||
|
<th>::totalPrice:: ::currency()::</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
$$export(/contractAdmin/stats/::c.id::?csv=1&stat=1)
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
|
||||||
|
<h3>::_("Products")::</h3>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
::foreach p c.getProducts()::
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
<img src="::p.getImage()::" style="width:64px;height:64px;" />
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>$$nullSafe(::p.ref::)</td>
|
||||||
|
<td>::p.name::</td>
|
||||||
|
<td>::p.stock::</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
$$edit(modifier,/product/edit/::p.id::)
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<h2>::_("Global view of orders - delivery of"):: ::hDate(date)::</h2>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
::foreach o orders::
|
||||||
|
|
||||||
|
::set total = 0::
|
||||||
|
<table class="table table-bordered table-hover table-striped" style="width:100%;">
|
||||||
|
<tr>
|
||||||
|
<th colspan="5">
|
||||||
|
<!--<div class="pull-right">$$export(::"/contractAdmin/ordersByProduct/"+o.contract.id+"?csv=1&d="+o.distrib.id::)</div>-->
|
||||||
|
<h4>::o.contract.name:: - ::o.contract._vendor.name::</h4>
|
||||||
|
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>::_("Quantities")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Reference")::</th>
|
||||||
|
<th>::_("Unit price incl. VAT")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::foreach m o.orders::
|
||||||
|
<tr>
|
||||||
|
<td class="col-md-1">
|
||||||
|
::m.quantity::
|
||||||
|
</td>
|
||||||
|
<td class="col-md-5">
|
||||||
|
::m.pname::
|
||||||
|
</td>
|
||||||
|
<td class="col-md-3">
|
||||||
|
$$nullSafe(::m.ref::)
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2">
|
||||||
|
::formatNum(m.priceTTC):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2">
|
||||||
|
::formatNum(m.totalTTC):: ::currency()::
|
||||||
|
::set total = total + m.totalTTC::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<tr style="background:#DDD;">
|
||||||
|
<th colspan="4">Total</th>
|
||||||
|
<th>::formatNum(total):: ::currency()::</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="article">
|
||||||
|
|
||||||
|
<h2>::_("Global view of orders - from"):: ::hDate(from):: ::_("to"):: ::hDate(to)::</h2>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
$$export(/contractAdmin/vendorsByTimeFrame/::from.toString().substr(0,10)::/::to.toString().substr(0,10)::?csv=1)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
::foreach o orders::
|
||||||
|
|
||||||
|
::set total = 0::
|
||||||
|
<table class="table table-bordered table-hover table-striped" style="width:100%;">
|
||||||
|
<tr>
|
||||||
|
<th colspan="6">
|
||||||
|
<!--<div class="pull-right">$$export(::"/contractAdmin/ordersByProduct/"+o.contract.id+"?csv=1&d="+o.distrib.id::)</div>-->
|
||||||
|
<h4>::o.contract.name:: - ::o.contract._vendor.name::</h4>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>::_("Quantities")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Reference")::</th>
|
||||||
|
<th>::_("Unit price incl. VAT")::</th>
|
||||||
|
<th>::_("Total HT")::</th>
|
||||||
|
<th>::_("Total TTC")::</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::foreach m o.orders::
|
||||||
|
<tr>
|
||||||
|
<td class="col-md-1">
|
||||||
|
::formatNum(m.quantity)::
|
||||||
|
</td>
|
||||||
|
<td class="col-md-4">
|
||||||
|
::m.pname::
|
||||||
|
</td>
|
||||||
|
<td class="col-md-1 ref">
|
||||||
|
$$nullSafe(::m.ref::)
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2">
|
||||||
|
::formatNum(m.priceTTC):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2">
|
||||||
|
::formatNum(m.totalHT):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2">
|
||||||
|
::formatNum(m.totalTTC):: ::currency()::
|
||||||
|
</td>
|
||||||
|
::set total = total + m.totalTTC::
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<tr style="background:#DDD;">
|
||||||
|
<th colspan="5">::_("Total")::</th>
|
||||||
|
<th>::formatNum(total):: ::currency()::</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
::use 'contractadmin/design.mtt'::
|
||||||
|
|
||||||
|
<h3>::_("Summary")::</h3>
|
||||||
|
|
||||||
|
<table class="table table-bordered" style="width:400px;">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::_("Contract type")::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if contract.type==0::
|
||||||
|
::_("CSA Contract")::
|
||||||
|
::else::
|
||||||
|
::_("Variable order contract")::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>::_("Start date")::</td>
|
||||||
|
<td>::hDate(contract.startDate)::</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>::_("End date")::</td>
|
||||||
|
<td>::hDate(contract.endDate)::</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
$$edit(::_("Edit")::,/contract/edit/::c.id::)
|
||||||
|
|
||||||
|
::if user.isAmapManager()::
|
||||||
|
$$edit(::_("Duplicate")::,/contractAdmin/duplicate/::c.id::)
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h4>::_("Farmer")::</h4>
|
||||||
|
<table class="table table-bordered" style="width:400px;">
|
||||||
|
<tr>
|
||||||
|
<td>::_("Name")::</td>
|
||||||
|
<td><b>::contract._vendor.name::</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>::_("Email")::</td>
|
||||||
|
<td>$$nullSafe(::contract._vendor.email::)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>::_("Phone")::</td>
|
||||||
|
<td>$$nullSafe(::contract._vendor.phone::)</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<h4><a href="/contractAdmin/products/::c.id::">::_("Products")::</a></h4>
|
||||||
|
<div style="max-height:400px;overflow:auto;">
|
||||||
|
<table class="table table-bordered" style="width:400px;">
|
||||||
|
::foreach p contract.getProducts(true)::
|
||||||
|
<tr>
|
||||||
|
<td><img src="::p.getImage()::" style="width:32px;height:32px;" /> </td>
|
||||||
|
<td>::p.getName()::</td>
|
||||||
|
<td>::formatNum(p.price):: ::currency()::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
::use 'design.mtt'::
|
||||||
|
|
||||||
|
<div id="content" class="col-md-8">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div id="content" class="col-md-12">
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||||
|
Hou là là !
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div id="content" class="col-md-12">
|
||||||
|
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<span class="glyphicon glyphicon-ok"></span>
|
||||||
|
Todo bem
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Boutons large</h3>
|
||||||
|
<button type="button" class="btn btn-lg btn-noAntiDoubleClick btn-default">noAntiDoubleClick</button>
|
||||||
|
<button type="button" class="btn btn-lg btn-default">Default</button>
|
||||||
|
<button type="button" class="btn btn-lg btn-primary">Primary</button>
|
||||||
|
<button type="button" class="btn btn-lg btn-success">Success</button>
|
||||||
|
<button type="button" class="btn btn-lg btn-info">Info</button>
|
||||||
|
<button type="button" class="btn btn-lg btn-warning">Warning</button>
|
||||||
|
<button type="button" class="btn btn-lg btn-danger">Danger</button>
|
||||||
|
<button type="button" class="btn btn-lg btn-link">Link</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Boutons default</h3>
|
||||||
|
<button type="button" class="btn btn-default">Default</button>
|
||||||
|
|
||||||
|
<!-- Provides extra visual weight and identifies the primary action in a set of buttons -->
|
||||||
|
<button type="button" class="btn btn-primary">Primary</button>
|
||||||
|
|
||||||
|
<!-- Indicates a successful or positive action -->
|
||||||
|
<button type="button" class="btn btn-success">Success</button>
|
||||||
|
|
||||||
|
<!-- Contextual button for informational alert messages -->
|
||||||
|
<button type="button" class="btn btn-info">Info</button>
|
||||||
|
|
||||||
|
<!-- Indicates caution should be taken with this action -->
|
||||||
|
<button type="button" class="btn btn-warning">Warning</button>
|
||||||
|
|
||||||
|
<!-- Indicates a dangerous or potentially negative action -->
|
||||||
|
<button type="button" class="btn btn-danger">Danger</button>
|
||||||
|
|
||||||
|
<!-- Deemphasize a button by making it look like a link while maintaining button behavior -->
|
||||||
|
<button type="button" class="btn btn-link">Link</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Boutons small</h3>
|
||||||
|
<button type="button" class="btn btn-sm btn-default">Default</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary">Primary</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-success">Success</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-info">Info</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-warning">Warning</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger">Danger</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-link">Link</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Boutons XS</h3>
|
||||||
|
<button type="button" class="btn btn-xs btn-default">Default</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-primary">Primary</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-success">Success</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-info">Info</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-warning">Warning</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-danger">Danger</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-link">Link</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h1>H1 Title</h1>
|
||||||
|
<h2>H2 Title</h2>
|
||||||
|
<h3>H3 Title</h3>
|
||||||
|
<h4>H4 Title</h4>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
|
||||||
|
<div class="panel panel-success">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">Panel title</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>
|
||||||
|
Type : <b>Gratuit jusqu'à 20 foyers</b> ( actuellement 1 foyers dans votre groupe)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button type="button" class="btn btn-primary"><span class="glyphicon glyphicon-chevron-right"></span> Primary</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="popover fade bottom in" role="tooltip" id="popover63647" style="top: 531px;left: 50%;display: block;">
|
||||||
|
<div class="arrow" style="left: 50%;"></div>
|
||||||
|
<h3 class="popover-title">Visite guidée du coordinateur <div class="pull-right">7/21</div></h3>
|
||||||
|
<div class="popover-content">
|
||||||
|
<p>Ici se trouve la liste des <b>contrats</b>.
|
||||||
|
Ils comportent une date de début, une date de fin, et représentent votre relation avec un producteur. <br />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
C'est ici que vous pourrez gérer :
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<ul>
|
||||||
|
<li>la liste de produits de ce producteur</li>
|
||||||
|
<li>les commandes des adhérents pour ce producteur</li>
|
||||||
|
<li>planifier les distributions</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<div class="footer">
|
||||||
|
|
||||||
|
<div class="pull-right">
|
||||||
|
<p><a class="btn btn-default btn-sm"><span class="glyphicon glyphicon-chevron-right"></span> Suivant</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="block" >
|
||||||
|
|
||||||
|
|
||||||
|
Le Lorem Ipsum est simplement du <strong>faux texte</strong> employé dans <a href="/">la composition</a>
|
||||||
|
et la mise en page avant impression. Le Lorem Ipsum est le faux texte standard de l'imprimerie depuis les années 1500,
|
||||||
|
quand un peintre anonyme assembla ensemble des morceaux de texte pour réaliser un livre spécimen de polices de texte.
|
||||||
|
Il n'a pas fait que survivre cinq siècles, mais s'est aussi adapté à la bureautique informatique, sans que son contenu n'en soit modifié.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<h3>Permanences</h3>
|
||||||
|
|
||||||
|
<p>N'oubliez pas de vous inscrire pour aider lors des livraisons</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Liste 1</li>
|
||||||
|
<li>Liste 2</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<p>
|
||||||
|
Attention, vous n'avez défini aucun mot de passe pour votre compte !
|
||||||
|
</p>
|
||||||
|
<a href="/user/definePassword" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-chevron-right"></span> Définir un mot de passe</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
$$today(Mercredi,15,Décembre,19,90,::user._amap._mainPlace::)
|
||||||
|
|
||||||
|
$$date(Mercredi,15,Décembre,19,::user._amap._mainPlace::)
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
::use 'base.mtt'::
|
||||||
|
|
||||||
|
::if getParam('__redirect')!=null::
|
||||||
|
<!-- need to login to go to this page-->
|
||||||
|
<script>$(function(){
|
||||||
|
_.loginBox('::raw getParam("__redirect")::');
|
||||||
|
});</script>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<div class="row header" style="margin-top:10px;">
|
||||||
|
<div class="col-md-8">
|
||||||
|
::set groupName = getCurrentGroup()::
|
||||||
|
::if groupName!=null && noGroup!=true::
|
||||||
|
<h1>::groupName::</h1>
|
||||||
|
::else::
|
||||||
|
<div style="margin-bottom:22px;" >
|
||||||
|
<a href="http://www.cagette.net" target="_blank">
|
||||||
|
<img src="/img/logo3.png" alt="::NAME::" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4 accountBlock">
|
||||||
|
::if user != null::
|
||||||
|
<div class="dropdown pull-right text-right userName" style="margin-top:16px;">
|
||||||
|
|
||||||
|
<a class="" style="margin-right:16px;" href="/user/choose?show=1">
|
||||||
|
<span class="glyphicon glyphicon-chevron-left"></span>::_("Change group")::
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">
|
||||||
|
<span class="glyphicon glyphicon-user"></span>
|
||||||
|
::user.getName()::
|
||||||
|
::if user.firstName2!=null::
|
||||||
|
::_("et"):: ::user.lastName2:: ::user.firstName2::
|
||||||
|
::end::
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenu1">
|
||||||
|
<li><a href="/user/logout"><span class="glyphicon glyphicon-log-out"></span> ::_("Log out")::</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
::else::
|
||||||
|
<div class="pull-right text-right">
|
||||||
|
<a href="/user/login" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-user"></span> ::_("Log in")::</a>
|
||||||
|
<a class="btn btn-default btn-sm" href="/group/map"><span class="glyphicon glyphicon-search"></span> ::_("Look for another group")::</a>
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
::if user != null && noGroup!=true::
|
||||||
|
|
||||||
|
<nav class="navbar navbar-default" ::cond user.getAmap()!=null::>
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#cagette-navbar">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="cagette-navbar">
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav navbar-left">
|
||||||
|
<li ::attr class if(category=="home") 'active' ::><a href="/">::_("Home")::</a></li>
|
||||||
|
<li ::attr class if(category=="contract" ) 'active' ::><a href="/contract">::_("My account")::</a></li>
|
||||||
|
<li ::attr class if(category=="amap") 'active' ::><a href="/amap">::_("Farmers")::</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li id="member" ::attr class if(category=="members") 'active' ::>
|
||||||
|
<a href="/member" ::cond user.canAccessMembership()::>::_("Members")::</a></li>
|
||||||
|
<li id="contractadmin" ::attr class if(category=="contractadmin") 'active' ::>
|
||||||
|
<a href="/contractAdmin" ::cond user.isContractManager(null)::>::_("Contracts")::</a></li>
|
||||||
|
<li id="messages" ::attr class if(category=="messages") 'active' ::>
|
||||||
|
<a href="/messages" ::cond user.canAccessMessages()::>::_("Messaging")::</a></li>
|
||||||
|
<li id="amapadmin" ::attr class if(category=="amapadmin") 'active' ::>
|
||||||
|
<a href="/amapadmin" ::cond user.isAmapManager()::>::_("Group")::</a></li>
|
||||||
|
<li ::cond user.isAdmin():: ::attr class if(category=="admin") 'active' ::>
|
||||||
|
<a href="/admin" >::_("Admin")::</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<div class="row">
|
||||||
|
<div id="content" class="col-md-12">
|
||||||
|
::foreach notification getMessages()::
|
||||||
|
<div class="alert ::if notification.error:: alert-danger ::else:: alert-success ::end::" ::cond notification::>
|
||||||
|
::if notification.error::
|
||||||
|
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||||
|
::else::
|
||||||
|
<span class="glyphicon glyphicon-ok"></span>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::raw notification.text::
|
||||||
|
</div>
|
||||||
|
::end::
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- extra notification block for plugins -->
|
||||||
|
::if extraNotifBlock!=null::
|
||||||
|
::raw extraNotifBlock::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
::raw __content__::
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--<div class="row">
|
||||||
|
<div class="col-md-12 alert-danger text-center" style="border-radius: 8px;margin:8px 0;padding:8px 0;">
|
||||||
|
<span class="glyphicon glyphicon-cog"></span>
|
||||||
|
Attention : Cagette.net sera indisponible le <b>mardi 7 août de 9:00 à 11:00</b>.
|
||||||
|
<br/>
|
||||||
|
Nous déménageons sur un nouveau serveur pour plus de rapidité et de fiabilité.
|
||||||
|
</div>
|
||||||
|
</div>-->
|
||||||
|
|
||||||
|
<div class="row" id="footer">
|
||||||
|
|
||||||
|
<div class="col-md-9" >
|
||||||
|
<a href="http://www.cagette.net" target="_blank">
|
||||||
|
<img src="/img/logo.png" alt="::_("Cagette.net")::"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3" >
|
||||||
|
::_("HELP")::
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="http://www.cagette.net/wiki" target="_blank">::_("Documentation")::</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.cagette.pro" target="_blank">Formations pour producteurs</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
::if (user!=null && user.isAmapManager()):: <a href="https://cagette.uservoice.com" target="_blank">::_("Propose features")::</a> ::end::
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!--<p style="position: absolute;display: block;top: 96px;width: 700px;">
|
||||||
|
<b>::raw _("Vous êtes producteur ?")::</b>
|
||||||
|
::raw _("Découvrez nos formations gratuites pour utiliser <b>Cagette Pro</b> ")::
|
||||||
|
<a href="http://alilo.fr/formations-producteurs" target="_blank" class="btn btn-xs btn-info">C'est parti !</a>
|
||||||
|
</p>-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div class="col-md-3" >
|
||||||
|
::_("FOLLOW US")::
|
||||||
|
<ul class="cagsocialmedia">
|
||||||
|
<li class="cagfb">
|
||||||
|
<a title="Facebook" href="https://www.facebook.com/cagette" target="_blank"> <i class="fa fa-facebook"></i></a>
|
||||||
|
</li>
|
||||||
|
<li class="cagtwitter">
|
||||||
|
<a title="Twitter" href="https://twitter.com/Cagettenet" target="_blank"> <i class="fa fa-twitter"></i></a>
|
||||||
|
</li>
|
||||||
|
<li class="cagyoutube">
|
||||||
|
<a title="Youtube" href="https://www.youtube.com/channel/UC3cvGxAUrbN9oSZmr1oZEaw" target="_blank"> <i class="fa fa-youtube"></i></a>
|
||||||
|
</li>
|
||||||
|
<li style="background-color:#333;">
|
||||||
|
<a title="Github" href="https://github.com/bablukid/cagette" target="_blank"> <i class="fa fa-github"></i></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div class="col-md-3">
|
||||||
|
::_("CONTACT US")::
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||||
|
<input type="hidden" name="cmd" value="_s-xclick"/>
|
||||||
|
<input type="hidden" name="hosted_button_id" value="S9KT7FQS7P622"/>
|
||||||
|
<input type="image" src="https://www.paypalobjects.com/fr_FR/FR/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal, le réflexe sécurité pour payer en ligne"/>
|
||||||
|
<img alt="" border="0" src="https://www.paypalobjects.com/fr_FR/i/scr/pixel.gif" width="1" height="1"/>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" id="subfooter">
|
||||||
|
<!--
|
||||||
|
::raw _("Cagette.net is made by <a href='https://www.alilo.fr' target='_blank'><img src='/img/alilo-trans-black.png' /></a>")::<br/>
|
||||||
|
-->
|
||||||
|
<span>version ::VERSION::-::getPlatform()::</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<!-- SQL LOG -->
|
||||||
|
<table class="sqlLog table table-bordered" ::cond sqlLog::>
|
||||||
|
::foreach r sqlLog::
|
||||||
|
<tr ::attr class if(r.bad) 'badSql'::>
|
||||||
|
<td>::r.t::ms</td>
|
||||||
|
<td>::r.length::</td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.explain::');return false;">Exp</a></td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.stack::');return false;">Stack</a></td>
|
||||||
|
<td>::r.sql::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
::if (false && user!=null && user.isAmapManager())::
|
||||||
|
<script>
|
||||||
|
// Include the UserVoice JavaScript SDK (only needed once on a page)
|
||||||
|
UserVoice=window.UserVoice||[];(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/sayZ1eIZLfSZUa3IwslHCw.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// UserVoice Javascript SDK developer documentation:
|
||||||
|
// https://www.uservoice.com/o/javascript-sdk
|
||||||
|
//
|
||||||
|
|
||||||
|
// Set colors
|
||||||
|
UserVoice.push(['set', {
|
||||||
|
accent_color: '#6aba2e',
|
||||||
|
trigger_color: 'white',
|
||||||
|
trigger_background_color: '#e2753a'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Identify the user and pass traits
|
||||||
|
// To enable, replace sample data with actual user traits and uncomment the line
|
||||||
|
UserVoice.push(['identify', {
|
||||||
|
//email: 'john.doe@example.com', // User’s email address
|
||||||
|
//name: 'John Doe', // User’s real name
|
||||||
|
//created_at: 1364406966, // Unix timestamp for the date the user signed up
|
||||||
|
//id: 123, // Optional: Unique id of the user (if set, this should not change)
|
||||||
|
//type: 'Owner', // Optional: segment your users by type
|
||||||
|
//account: {
|
||||||
|
// id: 123, // Optional: associate multiple users with a single account
|
||||||
|
// name: 'Acme, Co.', // Account name
|
||||||
|
// created_at: 1364406966, // Unix timestamp for the date the account was created
|
||||||
|
// monthly_rate: 9.99, // Decimal; monthly rate of the account
|
||||||
|
// ltv: 1495.00, // Decimal; lifetime value of the account
|
||||||
|
// plan: 'Enhanced' // Plan name for the account
|
||||||
|
//}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Add default trigger to the bottom-right corner of the window:
|
||||||
|
UserVoice.push(['addTrigger', { mode: 'smartvote', trigger_position: 'bottom-right' }]);
|
||||||
|
|
||||||
|
// Or, use your own custom trigger:
|
||||||
|
//UserVoice.push(['addTrigger', '#id', { mode: 'smartvote' }]);
|
||||||
|
|
||||||
|
// Autoprompt for Satisfaction and SmartVote (only displayed under certain conditions)
|
||||||
|
UserVoice.push(['autoprompt', {}]);
|
||||||
|
</script>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>::_("Attendance sheet")::</title>
|
||||||
|
<link href="/css/print.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>
|
||||||
|
::contract._amap.name:: -
|
||||||
|
::__("Distribution of the ::date::, from ::from:: to ::to::",{date:dDate(distrib.date),from:hHour(distrib.date),to:hHour(distrib.end)})::
|
||||||
|
</h2>
|
||||||
|
<h2>
|
||||||
|
::distrib._contract.name:: - ::distrib._contract._vendor.name::
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<span class="glyphicon glyphicon-map-marker"></span><b>::_("Place"):: : </b>
|
||||||
|
::distrib._place.toString()::, ::distrib._place.getAddress()::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
::set c = contract._contact::
|
||||||
|
::set v = contract._vendor::
|
||||||
|
<b>::_("Contact in charge of the contract") :</b> ::c.firstName:: ::c.lastName::, ::c.email::, ::c.phone::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>::_("Farmer"):: : </b>
|
||||||
|
::v.name:: ,
|
||||||
|
::if v.email!=null :: ::v.email:: ::end::,
|
||||||
|
::if v.phone!=null :: ::v.phone:: ::end::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>::_("Members on duty"):: : </b>
|
||||||
|
::if distrib._distributor1!=null:: ::distrib._distributor1.getName()::, ::end::
|
||||||
|
::if distrib._distributor2!=null:: ::distrib._distributor2.getName()::, ::end::
|
||||||
|
::if distrib._distributor3!=null:: ::distrib._distributor3.getName()::, ::end::
|
||||||
|
::if distrib._distributor4!=null:: ::distrib._distributor4.getName():: ::end::
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>::raw nl2br(distrib._contract._amap.txtDistrib)::</p>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-hover" style="width:100%;">
|
||||||
|
::set total = 0:: <!-- big total -->
|
||||||
|
::set subtotal = 0:: <!-- total per user -->
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
|
||||||
|
::set i = repeat.m.index::
|
||||||
|
|
||||||
|
<!-- name change -->
|
||||||
|
::if orders[i-1]==null || m.userName != orders[i-1].userName || m.userName2 != orders[i-1].userName2::
|
||||||
|
|
||||||
|
<tr class="name">
|
||||||
|
<td colspan="8">
|
||||||
|
<span style="font-size:1.3em;">
|
||||||
|
::set basket = getBasket(m.userId,distrib._place.id,distrib.date)::
|
||||||
|
<span ::cond basket!=null::>N°::basket.num:: - </span>
|
||||||
|
|
||||||
|
::m.userName::
|
||||||
|
</span>
|
||||||
|
|
||||||
|
::set u = getUser(m.userId)::
|
||||||
|
|
||||||
|
::if u !=null && u.phone!=null::
|
||||||
|
- Tél. $$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if m.userName2 != null::
|
||||||
|
::_("alternately with")::
|
||||||
|
<span style="font-size:1.3em;">
|
||||||
|
::m.userName2::
|
||||||
|
</span>
|
||||||
|
|
||||||
|
::set u = getUser(m.userId2):
|
||||||
|
::if u !=null::
|
||||||
|
- Tél. $$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
<th>::_("Ref")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("U.P.")::</th>
|
||||||
|
<th>::_("Sub-total")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::_("Signature")::</th>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- 1 order line -->
|
||||||
|
<tr class="order">
|
||||||
|
<td>
|
||||||
|
::if(m.quantity==0 && m.canceled)::
|
||||||
|
::_("Canceled")::
|
||||||
|
::else::
|
||||||
|
::raw m.smartQt::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
$$nullSafe(::m.productRef::)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::m.productName::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.subTotal):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- frais -->
|
||||||
|
::if m.percentageValue!=null::
|
||||||
|
::formatNum(m.fees):: ::currency()::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- total -->
|
||||||
|
::formatNum(m.total):: ::currency()::
|
||||||
|
::set total = total + m.total::
|
||||||
|
::set subtotal = subtotal + m.total::
|
||||||
|
</td>
|
||||||
|
<td style="width:20%;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- member total -->
|
||||||
|
::if (orders[i+1]==null || m.userName != orders[i+1].userName || m.userName2 != orders[i+1].userName2) ::
|
||||||
|
::if subtotal!=0::
|
||||||
|
<tr>
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<td class="total text-right">::_("TOTAL")::</td>
|
||||||
|
<td class="total">::formatNum(subtotal):: ::currency()::</td>
|
||||||
|
<td></td>
|
||||||
|
::set subtotal = 0::
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- big total -->
|
||||||
|
<tr style="font-size:1.3em;">
|
||||||
|
<td class="text-right">::_("Total order:"):: </td>
|
||||||
|
<td>::formatNum(total):: ::currency()::</td>
|
||||||
|
<td colspan="6"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p class="hidden"><i>::_("Push on \"Control + P\" to print this page.")::</i></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>::_("Attendance sheet")::</title>
|
||||||
|
<link href="/css/print.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>::user._amap.name::</h1>
|
||||||
|
<h2>Livraison du ::hDate(date)::</h2>
|
||||||
|
|
||||||
|
<table style="width:100%;">
|
||||||
|
::set name = ""::
|
||||||
|
::set name2 = ""::
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
<tr>
|
||||||
|
<th>::_("Name")::</th>
|
||||||
|
<th>::_("Contact details")::</th>
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("U.P.")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
<th>::_("Signature")::</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
|
||||||
|
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
::set class="name"::
|
||||||
|
::else::
|
||||||
|
::set class=""::
|
||||||
|
::end::
|
||||||
|
<tr class="::class::">
|
||||||
|
<td>
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
|
||||||
|
::set basket = getBasket(m.userId,place.id,date)::
|
||||||
|
<span ::cond basket!=null::>::_("Num"):: ::basket.num:: - </span>
|
||||||
|
|
||||||
|
<b>::m.userName::</b>
|
||||||
|
::if m.userName2 != null::
|
||||||
|
<br />::_("alternately with"):: <b>::m.userName2::</b>
|
||||||
|
::end::
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
::set u = getUser(m.userId):
|
||||||
|
::if u !=null::
|
||||||
|
$$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if m.userName2 != null::
|
||||||
|
::set u = getUser(m.userId2):
|
||||||
|
::if u !=null::
|
||||||
|
<br />$$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
::set name = m.userName ::
|
||||||
|
::set name2 = m.userName2 ::
|
||||||
|
<td>
|
||||||
|
::if(m.quantity==0 && m.canceled)::
|
||||||
|
::_("Canceled")::
|
||||||
|
::else::
|
||||||
|
::raw m.smartQt::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::set productNum = productNum+m.quantity::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::m.productName::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.fees!=null::
|
||||||
|
::formatNum(m.fees):: ::currency()::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.total):: ::currency()::
|
||||||
|
::set total = total + m.total::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td style="width:20%;"></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!--TOTAL-->
|
||||||
|
::set next = orders[repeat.m.index+1]::
|
||||||
|
::if next==null || next.userName!=name || next.userName2!=name2::
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="total">::formatNum(productNum)::</td>
|
||||||
|
<td class="total" colspan="2"></td>
|
||||||
|
<td class="total">::_("TOTAL")::</td>
|
||||||
|
<td class="total">::formatNum(total):: ::currency()::</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- PAIEMENTS -->
|
||||||
|
<tr ::cond user._amap.hasPayments()::>
|
||||||
|
|
||||||
|
<!-- PAYMENT CHECK FOR SHOP MODE -->
|
||||||
|
|
||||||
|
::if (user._amap.hasShopMode())::
|
||||||
|
|
||||||
|
|
||||||
|
<td></td>
|
||||||
|
<th>::_("Payment:"):: </th>
|
||||||
|
<td colspan="6">
|
||||||
|
<table style="width:100%;border:1px solid #CCC;">
|
||||||
|
::if(basket!=null)::
|
||||||
|
::set tpaid=0::
|
||||||
|
::set op = basket.getOrderOperation(false)::
|
||||||
|
::if op!=null::
|
||||||
|
::foreach p op.getRelatedPayments()::
|
||||||
|
<tr>
|
||||||
|
<td style="min-width:30px;">
|
||||||
|
::if(p.pending==true)::
|
||||||
|
<img src="/img/tick_no.png"/>
|
||||||
|
::else::
|
||||||
|
<img src="/img/tick_ok_me.png"/>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(p.amount):: ::currency()::
|
||||||
|
::set tpaid = p.amount+tpaid::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::p.name::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- bug : 2 vars are the same but == doesnt work -->
|
||||||
|
::set total = numClean(total)::
|
||||||
|
::set tpaid = numClean(tpaid)::
|
||||||
|
|
||||||
|
::if(total!=tpaid)::
|
||||||
|
|
||||||
|
<tr style="background-color:#EEE;">
|
||||||
|
<td><img src="/img/tick_no.png"/></td>
|
||||||
|
<td>::formatNum(total-tpaid):: ::currency()::</td>
|
||||||
|
<td>
|
||||||
|
::if(total>tpaid)::
|
||||||
|
::_("To be paid"):: ::formatNum(total-tpaid):: ::currency()::
|
||||||
|
::else::
|
||||||
|
::_("Pay back"):: ::formatNum(tpaid-total):: ::currency()::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
::else::
|
||||||
|
<!-- PAYMENT CHECK FOR STANDARD MODE -->
|
||||||
|
<td></td>
|
||||||
|
::set ua = u.getUserAmap(user._amap)::
|
||||||
|
::set balance = ua.balance::
|
||||||
|
<th>::_("Balance of the member:"):: ::ua.balance:: ::currency()::</th>
|
||||||
|
<td colspan="6">
|
||||||
|
::set pending = 0::
|
||||||
|
|
||||||
|
<table style="width:100%;border:1px solid #CCC;">
|
||||||
|
<!-- payment to confirm -->
|
||||||
|
::foreach p ua.getLastOperations(5)::
|
||||||
|
::if p.type==2 && p.pending==true::
|
||||||
|
<tr>
|
||||||
|
<td style="width:30px;">
|
||||||
|
::if(p.pending==true)::
|
||||||
|
<img src="/img/tick_no.png"/>
|
||||||
|
::else::
|
||||||
|
<img src="/img/tick_ok_me.png"/>
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(p.amount):: ::currency()::
|
||||||
|
- ::p.name::
|
||||||
|
|
||||||
|
::set pending = pending + p.amount::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
<!-- need to pay more or cash back -->
|
||||||
|
::if(balance<0 && balance+pending!=0)::
|
||||||
|
<tr style="background-color:#EEE;">
|
||||||
|
<td style="width:30px;"><img src="/img/tick_no.png"/></td>
|
||||||
|
<td>
|
||||||
|
::if(balance+pending<0)::
|
||||||
|
::_("To be paid"):: ::formatNum(abs(balance+pending)):: ::currency():: pour équilibrer le solde
|
||||||
|
::else::
|
||||||
|
::_("Pay back"):: ::formatNum(balance+pending):: ::currency():: pour équilibrer le solde
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>::raw nl2br(user.getAmap().txtDistrib)::</p>
|
||||||
|
|
||||||
|
<p class="hidden">
|
||||||
|
<i>::_("Push on \"Control + P\" to print this page.")::</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<table class="" ::cond sqlLog::>
|
||||||
|
::foreach r sqlLog::
|
||||||
|
<tr ::attr class if(r.bad) 'badSql'::>
|
||||||
|
<td>::r.t::ms</td>
|
||||||
|
<td>::r.length::</td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.explain::');return false;">Exp</a></td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.stack::');return false;">Stack</a></td>
|
||||||
|
<td>::r.sql::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>::_("Attendance sheet")::</title>
|
||||||
|
<link href="/css/print.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- page head-->
|
||||||
|
::fill head::
|
||||||
|
<td >
|
||||||
|
<h1 style='text-align:right;'>::user._amap.name::</h1>
|
||||||
|
<h2 style='text-align:right;'>::_("Delivery of the"):: ::hDate(date)::</h2>
|
||||||
|
</td>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- table header -->
|
||||||
|
::fill tableHeader::
|
||||||
|
<tr>
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("U.P.")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
|
||||||
|
<th>::_("Signature")::</th>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::set name = ""::
|
||||||
|
::set name2 = ""::
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
|
||||||
|
::if name != ""::
|
||||||
|
::raw "</table> <hr />"::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::raw "<table style='width:100%;'>" ::
|
||||||
|
<tr>
|
||||||
|
<td style='width:33%;'>
|
||||||
|
|
||||||
|
<b style="font-size:150%;">
|
||||||
|
::set basket = getBasket(m.userId,place.id,date)::
|
||||||
|
<span ::cond basket!=null::>::_("Num"):: ::basket.num:: - </span>
|
||||||
|
::m.userName::
|
||||||
|
</b>
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::set u = getUser(m.userId):
|
||||||
|
::if u!=null && u.phone!=null::
|
||||||
|
Tél: $$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td style='text-align:right;'>
|
||||||
|
<b>::user._amap.name::</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::if m.userName2 != null::
|
||||||
|
::_("alternately with")::<br/>
|
||||||
|
<b>::m.userName2::</b>
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.userName2 != null::
|
||||||
|
::set u = getUser(m.userId2):
|
||||||
|
::if u !=null::
|
||||||
|
$$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td style='text-align:right;'>
|
||||||
|
<b>::_("Delivery of the"):: ::hDate(date)::</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::raw "</table>" ::
|
||||||
|
|
||||||
|
::raw "<table style='width:100%;'>"::
|
||||||
|
::raw tableHeader::
|
||||||
|
|
||||||
|
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
::set class="name"::
|
||||||
|
::else::
|
||||||
|
::set class=""::
|
||||||
|
::end::
|
||||||
|
<tr class="::class::">
|
||||||
|
<td style="font-size: ::fontRatio::%;border-bottom: 2px solid #AAA;">
|
||||||
|
::if(m.quantity==0 && m.canceled)::
|
||||||
|
::_("Canceled")::
|
||||||
|
::else::
|
||||||
|
::raw m.smartQt::
|
||||||
|
::end::
|
||||||
|
::set productNum = productNum+m.quantity::
|
||||||
|
</td>
|
||||||
|
<td style="font-size: ::fontRatio::%;border-bottom: 2px solid #AAA;">
|
||||||
|
::m.productName::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.fees!=null::
|
||||||
|
::formatNum(m.fees):: ::currency()::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.total):: ::currency()::
|
||||||
|
::set total = total + m.total::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td style="width:20%;"></td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::set name = m.userName ::
|
||||||
|
::set name2 = m.userName2 ::
|
||||||
|
|
||||||
|
<!--TOTAL-->
|
||||||
|
::set next = orders[repeat.m.index+1]::
|
||||||
|
::if next==null || next.userName!=name || next.userName2!=name2::
|
||||||
|
<tr>
|
||||||
|
<td class="total">::formatNum(productNum)::</td>
|
||||||
|
<td class="total" colspan="2"></td>
|
||||||
|
<td class="total">::_("TOTAL")::</td>
|
||||||
|
<td class="total" style="font-size: ::fontRatio::%;">::formatNum(total):: ::currency()::</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<p>::raw nl2br(user.getAmap().txtDistrib)::</p>
|
||||||
|
|
||||||
|
<p class="hidden"><i>::_("Push on \"Control + P\" to print this page.")::</i></p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<table class="" ::cond sqlLog::>
|
||||||
|
::foreach r sqlLog::
|
||||||
|
<tr ::attr class if(r.bad) 'badSql'::>
|
||||||
|
<td>::r.t::ms</td>
|
||||||
|
<td>::r.length::</td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.explain::');return false;">Exp</a></td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.stack::');return false;">Stack</a></td>
|
||||||
|
<td>::r.sql::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>::_("Attendance sheet")::</title>
|
||||||
|
<link href="/css/print.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- page head-->
|
||||||
|
::fill head::
|
||||||
|
<td >
|
||||||
|
<h1 style='text-align:right;'>::user._amap.name::</h1>
|
||||||
|
<h2 style='text-align:right;'>::_("Delivery of the"):: ::hDate(date)::</h2>
|
||||||
|
</td>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<!-- table header -->
|
||||||
|
::fill tableHeader::
|
||||||
|
<tr>
|
||||||
|
<th>::_("Contract")::</th>
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("U.P.")::</th>
|
||||||
|
<th>::_("Fees")::</th>
|
||||||
|
<th>::_("Total")::</th>
|
||||||
|
|
||||||
|
<th>::_("Signature")::</th>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::set name = ""::
|
||||||
|
::set name2 = ""::
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
::set currentContract = ""::
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
|
||||||
|
::if name != ""::
|
||||||
|
::raw "</table> <hr />"::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::raw "<table style='width:100%;'>" ::
|
||||||
|
<tr>
|
||||||
|
<td style='width:33%;'>
|
||||||
|
<b style="font-size:150%;">
|
||||||
|
::set basket = getBasket(m.userId,place.id,date)::
|
||||||
|
<span ::cond basket!=null::>N°::basket.num:: - </span>
|
||||||
|
::m.userName::
|
||||||
|
</b>
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::set u = getUser(m.userId):
|
||||||
|
::if u!=null && u.phone!=null::
|
||||||
|
Tél: $$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td style='text-align:right;'>
|
||||||
|
<b>::user._amap.name::</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
::if m.userName2 != null::
|
||||||
|
::_("alternately with")::<br/>
|
||||||
|
<b>::m.userName2::</b>
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.userName2 != null::
|
||||||
|
::set u = getUser(m.userId2):
|
||||||
|
::if u !=null::
|
||||||
|
$$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td style='text-align:right;'>
|
||||||
|
<b>::_("Delivery of the"):: ::hDate(date)::</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
::raw "</table>" ::
|
||||||
|
|
||||||
|
::raw "<table style='width:100%;'>"::
|
||||||
|
::raw tableHeader::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
::set class="name"::
|
||||||
|
::set currentContract = ""::
|
||||||
|
|
||||||
|
::else::
|
||||||
|
::set class=""::
|
||||||
|
::end::
|
||||||
|
<tr class="::class::">
|
||||||
|
::if currentContract != short(m.contractName,40) ::
|
||||||
|
::if currentContract == "" ::
|
||||||
|
::set td_style="font-size:" + fontRatio + "%" ::
|
||||||
|
::else::
|
||||||
|
::set td_style="font-size:" + fontRatio + "%;border-top: 2px solid #AAA;" ::
|
||||||
|
::end::
|
||||||
|
<td style="::td_style::" >
|
||||||
|
::short(m.contractName,40)::
|
||||||
|
</td>
|
||||||
|
::set currentContract = short(m.contractName,40)::
|
||||||
|
::else::
|
||||||
|
<td> </td>
|
||||||
|
::end::
|
||||||
|
<td style="font-size: ::fontRatio::%;border-bottom: 2px solid #AAA;">
|
||||||
|
::if(m.quantity==0 && m.canceled)::
|
||||||
|
::_("Canceled")::
|
||||||
|
::else::
|
||||||
|
::raw m.smartQt::
|
||||||
|
::end::
|
||||||
|
::set productNum = productNum+m.quantity::
|
||||||
|
</td>
|
||||||
|
<td style="font-size: ::fontRatio::%;border-bottom: 2px solid #AAA;">
|
||||||
|
::m.productName::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.productPrice):: ::currency()::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.fees!=null::
|
||||||
|
::formatNum(m.fees):: ::currency()::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::formatNum(m.total):: ::currency()::
|
||||||
|
::set total = total + m.total::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td style="width:20%;"></td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
::set name = m.userName ::
|
||||||
|
::set name2 = m.userName2 ::
|
||||||
|
|
||||||
|
<!--TOTAL-->
|
||||||
|
::set next = orders[repeat.m.index+1]::
|
||||||
|
::if next==null || next.userName!=name || next.userName2!=name2::
|
||||||
|
<tr>
|
||||||
|
<td style="border-top: 2px solid #AAA;"></td>
|
||||||
|
<td class="total">::formatNum(productNum)::</td>
|
||||||
|
<td class="total" colspan="2"></td>
|
||||||
|
<td class="total">::_("TOTAL")::</td>
|
||||||
|
<td class="total" style="font-size: ::fontRatio::%;">::formatNum(total):: ::currency()::</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
<p>::raw nl2br(user.getAmap().txtDistrib)::</p>
|
||||||
|
|
||||||
|
<p class="hidden"><i>::_("Push on \"Control + P\" to print this page.")::</i></p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<table class="" ::cond sqlLog::>
|
||||||
|
::foreach r sqlLog::
|
||||||
|
<tr ::attr class if(r.bad) 'badSql'::>
|
||||||
|
<td>::r.t::ms</td>
|
||||||
|
<td>::r.length::</td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.explain::');return false;">Exp</a></td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.stack::');return false;">Stack</a></td>
|
||||||
|
<td>::r.sql::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>::_("Attendance sheet")::</title>
|
||||||
|
<link href="/css/print.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>::user._amap.name::</h1>
|
||||||
|
<h2>Livraison du ::hDate(date)::</h2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<table style="width:100%;">
|
||||||
|
::set name = ""::
|
||||||
|
::set name2 = ""::
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
<tr style="font-size:120%">
|
||||||
|
<th>::_("Name")::</th>
|
||||||
|
<th>::_("Contact details")::</th>
|
||||||
|
<th>::_("Qty")::</th>
|
||||||
|
<th>::_("Product")::</th>
|
||||||
|
<th>::_("Signature")::</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::foreach m orders::
|
||||||
|
|
||||||
|
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
::set class="name"::
|
||||||
|
::else::
|
||||||
|
::set class=""::
|
||||||
|
::end::
|
||||||
|
<tr class="::class::">
|
||||||
|
|
||||||
|
<td style="font-size:120%">
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
<b>
|
||||||
|
::set basket = getBasket(m.userId,place.id,date)::
|
||||||
|
<span ::cond basket!=null::>N°::basket.num:: - </span>
|
||||||
|
|
||||||
|
::m.userName::
|
||||||
|
</b>
|
||||||
|
::if m.userName2 != null::
|
||||||
|
<br />::_("alternately with"):: <b>::m.userName2::</b>
|
||||||
|
::end::
|
||||||
|
::set total = 0::
|
||||||
|
::set productNum = 0::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::if m.userName != name || m.userName2 != name2::
|
||||||
|
::set u = getUser(m.userId):
|
||||||
|
::if u !=null::
|
||||||
|
$$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
|
||||||
|
::if m.userName2 != null::
|
||||||
|
::set u = getUser(m.userId2):
|
||||||
|
::if u !=null::
|
||||||
|
<br />$$nullSafe(::u.phone::)
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</td>
|
||||||
|
::set name = m.userName ::
|
||||||
|
::set name2 = m.userName2 ::
|
||||||
|
<td>
|
||||||
|
::if(m.quantity==0 && m.canceled)::
|
||||||
|
::_("Canceled")::
|
||||||
|
::else::
|
||||||
|
::raw m.smartQt::
|
||||||
|
::end::
|
||||||
|
::set productNum = productNum+m.quantity::
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
::m.productName::
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td style="width:20%;"></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
::set next = orders[repeat.m.index+1]::
|
||||||
|
::end::
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>::raw nl2br(user.getAmap().txtDistrib)::</p>
|
||||||
|
|
||||||
|
<p class="hidden">
|
||||||
|
<i>::_("Push on \"Control + P\" to print this page.")::</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<table class="" ::cond sqlLog::>
|
||||||
|
::foreach r sqlLog::
|
||||||
|
<tr ::attr class if(r.bad) 'badSql'::>
|
||||||
|
<td>::r.t::ms</td>
|
||||||
|
<td>::r.length::</td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.explain::');return false;">Exp</a></td>
|
||||||
|
<td><a href="#" onclick="javascript:alert('::r.stack::');return false;">Stack</a></td>
|
||||||
|
<td>::r.sql::</td>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>::_("Attendance sheet")::</title>
|
||||||
|
<link href="/css/print.css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>
|
||||||
|
::contract._amap.name:: -
|
||||||
|
::__("Distribution of the ::date::, from ::from:: to ::to::",{date:dDate(distrib.date),from:hHour(distrib.date),to:hHour(distrib.end)})::
|
||||||
|
</h2>
|
||||||
|
<h2>
|
||||||
|
::distrib._contract.name:: - ::distrib._contract._vendor.name::
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<span class="glyphicon glyphicon-map-marker"></span><b>::_("Place"):: : </b>
|
||||||
|
::distrib._place.toString()::, ::distrib._place.getAddress()::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
::set c = contract._contact::
|
||||||
|
::set v = contract._vendor::
|
||||||
|
<b>::_("Contact in charge of the contract") :</b> ::c.firstName:: ::c.lastName::, ::c.email::, ::c.phone::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>::_("Farmer"):: : </b>
|
||||||
|
::v.name:: ,
|
||||||
|
::if v.email!=null :: ::v.email:: ::end::,
|
||||||
|
::if v.phone!=null :: ::v.phone:: ::end::
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>::_("Members on duty"):: : </b>
|
||||||
|
::if distrib._distributor1!=null:: ::distrib._distributor1.getName()::, ::end::
|
||||||
|
::if distrib._distributor2!=null:: ::distrib._distributor2.getName()::, ::end::
|
||||||
|
::if distrib._distributor3!=null:: ::distrib._distributor3.getName()::, ::end::
|
||||||
|
::if distrib._distributor4!=null:: ::distrib._distributor4.getName():: ::end::
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-hover" style="width:100%;">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
::foreach p products::
|
||||||
|
<th>
|
||||||
|
::p.name::<br/>
|
||||||
|
::p.ref::
|
||||||
|
</th>
|
||||||
|
::end::
|
||||||
|
<th>TOTAL</th>
|
||||||
|
</tr>
|
||||||
|
::foreach user users::
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
::set basket = getBasket(user.id,distrib._place.id,distrib.date)::
|
||||||
|
<span ::cond basket!=null::>N°::basket.num:: - </span>
|
||||||
|
|
||||||
|
::user.getCoupleName()::
|
||||||
|
|
||||||
|
</th>
|
||||||
|
::foreach p products::
|
||||||
|
<td>
|
||||||
|
::set o = orders.get(user.id).get(p.id)::
|
||||||
|
|
||||||
|
::if(o!=null)::
|
||||||
|
::raw o.smartQt::
|
||||||
|
::end::
|
||||||
|
</td>
|
||||||
|
::end::
|
||||||
|
<th>::formatNum(totalByUser(user.id)):: ::currency()::</th>
|
||||||
|
</tr>
|
||||||
|
::end::
|
||||||
|
<tr>
|
||||||
|
<th>TOTAL</th>
|
||||||
|
::foreach p products::
|
||||||
|
<th>
|
||||||
|
::set q = totalByProduct(p.id)::
|
||||||
|
::if(p.qt!=null && (p.hasFloatQt || p.variablePrice) )::
|
||||||
|
::q * p.qt:: ::unit(p._unitType,false)::
|
||||||
|
::else::
|
||||||
|
::q::
|
||||||
|
::end::
|
||||||
|
</th>
|
||||||
|
::end::
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>::raw nl2br(distrib._contract._amap.txtDistrib)::</p>
|
||||||
|
<p class="hidden"><i>::_("Push on \"Control + P\" to print this page.")::</i></p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user