package SL::Controller::RequirementSpec;
use strict;
+use utf8;
use parent qw(SL::Controller::Base);
use SL::DB::RequirementSpec;
use SL::Helper::Flash;
use SL::Locale::String;
+use SL::Template::LaTeX;
use Rose::Object::MakeMethods::Generic
(
$self->js->redirect_to($self->url_for(action => 'show', id => $self->requirement_spec->id))->render($self);
}
+sub action_create_pdf {
+ my ($self, %params) = @_;
+
+ my %result = SL::Template::LaTeX->parse_and_create_pdf('requirement_spec.tex', SELF => $self, rspec => $self->requirement_spec);
+
+ $::form->error(t8('Conversion to PDF failed: #1', $result{error})) if $result{error};
+
+ my $attachment_name = $self->requirement_spec->type->description . ' ' . ($self->requirement_spec->working_copy_id || $self->requirement_spec->id);
+ $attachment_name .= ' (v' . $self->requirement_spec->version->version_number . ')' if $self->requirement_spec->version;
+ $attachment_name .= '.pdf';
+ $attachment_name =~ s/[^\wäöüÄÖÜß \-\+\(\)\[\]\{\}\.,]+/_/g;
+
+ $self->send_file($result{file_name}, type => 'application/pdf', name => $attachment_name);
+ unlink $result{file_name};
+}
+
#
# filters
#
return map { [ $_->fb_number . ' ' . $_->title,
[ map { ( $self->create_dependency_item($_),
- map { $self->create_dependency_item($_, '->') } @{ $_->sorted_children })
- } @{ $_->sorted_children } ] ]
+ map { $self->create_dependency_item($_, '->') } @{ $_->children_sorted })
+ } @{ $_->children_sorted } ] ]
} @{ $self->item->requirement_spec->sections };
}
use SL::DB::MetaSetup::RequirementSpec;
use SL::DB::Manager::RequirementSpec;
use SL::Locale::String;
+use SL::Util qw(_hashify);
__PACKAGE__->meta->add_relationship(
items => {
return 1;
}
-sub text_blocks_for_position {
- my ($self, $output_position) = @_;
+sub text_blocks_sorted {
+ my ($self, %params) = _hashify(1, @_);
- return [ sort { $a->position <=> $b->position } grep { $_->output_position == $output_position } @{ $self->text_blocks } ];
+ my @text_blocks = @{ $self->text_blocks };
+ @text_blocks = grep { $_->output_position == $params{output_position} } @text_blocks if exists $params{output_position};
+ @text_blocks = sort { $a->position <=> $b->position } @text_blocks;
+
+ return wantarray ? @text_blocks : \@text_blocks;
}
-sub sections {
+sub sections_sorted {
my ($self, @rest) = @_;
croak "This sub is not a writer" if @rest;
- return [ sort { $a->position <=> $b->position } grep { !$_->parent_id } @{ $self->items } ];
+ my @sections = sort { $a->position <=> $b->position } grep { !$_->parent_id } @{ $self->items };
+ return wantarray ? @sections : \@sections;
}
+sub sections { §ions_sorted; }
+
sub displayable_name {
my ($self) = @_;
return sprintf('%s: "%s"', $self->type->description, $self->title);
}
+sub versioned_copies_sorted {
+ my ($self, %params) = _hashify(1, @_);
+
+ my @copies = @{ $self->versioned_copies };
+ @copies = grep { $_->version->version_number <= $params{max_version_number} } @copies if $params{max_version_number};
+ @copies = sort { $a->version->version_number <=> $b->version->version_number } @copies;
+
+ return wantarray ? @copies : \@copies;
+}
+
sub create_copy {
my ($self, %params) = @_;
sub create_version {
my ($self, %attributes) = @_;
+ croak "Cannot work on a versioned copy" if $self->working_copy_id;
+
my ($copy, $version);
my $ok = $self->db->do_transaction(sub {
delete $attributes{version_number};
sub invalidate_version {
my ($self, %params) = @_;
- $::lxdebug->message(0, "Invalidate version called for id " . $self->id . " version " . $self->version_id);
- $::lxdebug->show_backtrace(1);
+ croak "Cannot work on a versioned copy" if $self->working_copy_id;
return if !$self->id || !$self->version_id;
$self->update_attributes(version_id => undef);
return @errors;
}
-sub sorted_children {
+sub children_sorted {
my ($self, @args) = @_;
croak "Not a writer" if @args;
- return [ sort { $a->position <=> $b->position } @{ $self->children } ];
+ my @children = sort { $a->position <=> $b->position } $self->children;
+ return wantarray ? @children : \@children;
}
sub section {
sub requirement_spec_item_jstree_data {
my ($self, $item, %params) = @_;
- my @children = map { $self->requirement_spec_item_jstree_data($_, %params) } @{ $item->sorted_children };
+ my @children = map { $self->requirement_spec_item_jstree_data($_, %params) } @{ $item->children_sorted };
my $type = !$item->parent_id ? 'section' : 'function-block';
my $class = $type . '-context-menu';
$class .= ' flagged' if $item->is_flagged;
.context-menu-item.icon-close { background-image: url("../image/document-close.png"); }
.context-menu-item.icon-save { background-image: url("../image/document-save.png"); }
.context-menu-item.icon-revert { background-image: url("../image/edit-undo.png"); }
+.context-menu-item.icon-pdf { background-image: url("../image/application-pdf.png"); }
/* ------------------------------------------------------------ */
/* Sections & function blocks */
// ---------------------------- general actions ----------------------------
// -------------------------------------------------------------------------
-function download_reqspec_pdf(key, opt) {
+function create_reqspec_pdf(key, opt) {
var data = {
- action: "RequirementSpec/download_pdf",
+ action: "RequirementSpec/create_pdf",
id: $('#requirement_spec_id').val()
};
$.download("controller.pl", data);
}
function create_pdf_for_versioned_copy_ajax_call(key, opt) {
- // TODO: create_pdf_for_versioned_copy_ajax_call
+ var data = {
+ action: "RequirementSpec/create_pdf",
+ id: find_versioned_copy_id(opt.$trigger)
+ };
+ $.download("controller.pl", data);
return true;
}
sep98: "---------"
, general_actions: { name: kivi.t8('Requirement spec actions'), className: 'context-menu-heading' }
// , sep99: "---------"
+ , create_pdf: { name: kivi.t8('Create PDF'), icon: "pdf", callback: create_reqspec_pdf }
, create_version: { name: kivi.t8('Create new version'), icon: "new", callback: create_requirement_spec_version, disabled: disable_requirement_spec_commands }
, copy_reqspec: { name: kivi.t8('Copy requirement spec'), icon: "copy", callback: copy_reqspec }
, delete_reqspec: { name: kivi.t8('Delete requirement spec'), icon: "delete", callback: delete_reqspec }
$.contextMenu({
selector: '.versioned-copy-context-menu',
items: $.extend({
- heading: { name: kivi.t8('Version actions'), className: 'context-menu-heading' }
- // create_pdf: { name: kivi.t8('Create PDF'), icon: "pdf", callback: create_pdf_for_versioned_copy_ajax_call }
- , revert_to_version: { name: kivi.t8('Revert to version'), icon: "revert", callback: revert_to_versioned_copy_ajax_call, disabled: disable_versioned_copy_item_commands }
+ heading: { name: kivi.t8('Version actions'), className: 'context-menu-heading' }
+ , create_version_pdf: { name: kivi.t8('Create PDF'), icon: "pdf", callback: create_pdf_for_versioned_copy_ajax_call }
+ , revert_to_version: { name: kivi.t8('Revert to version'), icon: "revert", callback: revert_to_versioned_copy_ajax_call, disabled: disable_versioned_copy_item_commands }
}, general_actions)
});
}
'Continue' => 'Weiter',
'Contra' => 'gegen',
'Conversion of "birthday" contact person attribute' => 'Umstellung des Kontaktpersonenfeldes "Geburtstag"',
+ 'Conversion to PDF failed: #1' => 'Konvertierung zu PDF schlug fehl: #1',
'Copies' => 'Kopien',
'Copy file from #1 to #2 failed: #3' => 'Kopieren der Datei von #1 nach #2 schlug fehl: #3',
'Copy' => 'Kopieren',
--- /dev/null
+# @tag: requirement_specs_print_templates
+# @description: requirement_specs_print_templates
+# @depends: requirement_specs
+package SL::DBUpgrade2::requirement_specs_print_templates;
+
+use strict;
+use utf8;
+
+use parent qw(SL::DBUpgrade2::Base);
+
+sub run {
+ my ($self) = @_;
+
+ $self->add_print_templates(
+ 'templates/print/Standard',
+ qw(images/draft.png images/hintergrund_seite1.png images/hintergrund_seite2.png images/schachfiguren.jpg kivitendo.sty requirement_spec.tex)
+ );
+
+ return 1;
+}
+
+1;
--- /dev/null
+\ProvidesFile{kivitendo.sty}
+\usepackage{colortbl}
+\usepackage{eurosym}
+\usepackage{german}
+\usepackage{graphicx}
+\usepackage{ifthen}
+\usepackage[utf8]{inputenc}
+\usepackage{latexsym}
+\usepackage{longtable}
+\usepackage{textcomp}
+
+%% Paketoptionen
+\newboolean{defaultbg}\setboolean{defaultbg}{true}
+\newboolean{draftbg}
+\newboolean{reqspeclogo}
+\newboolean{secondpagelogo}
+\DeclareOption{nologo}{\setboolean{defaultbg}{false}}
+\DeclareOption{draftlogo}{\setboolean{defaultbg}{false}\setboolean{draftbg}{true}}
+\DeclareOption{reqspeclogo}{\setboolean{reqspeclogo}{true}}
+\DeclareOption{secondpagelogo}{\setboolean{defaultbg}{false}\setboolean{secondpagelogo}{true}}
+\ProcessOptions
+
+%% Seitenlayout
+\setlength{\voffset}{-1.5cm}
+\setlength{\hoffset}{-2.5cm}
+\setlength{\topmargin}{0cm}
+\setlength{\headheight}{0.5cm}
+\setlength{\headsep}{1cm}
+\setlength{\topskip}{0pt}
+\setlength{\oddsidemargin}{2cm}
+\setlength{\textwidth}{16.4cm}
+\setlength{\textheight}{25cm}
+\setlength{\footskip}{1cm}
+\setlength{\parindent}{0pt}
+\setlength{\tabcolsep}{0.2cm}
+
+\setlength{\unitlength}{1cm}
+
+\newcommand{\kivitendobgsettings}{%
+ \setlength{\headsep}{2.5cm}
+ \setlength{\textheight}{22.5cm}
+ \setlength{\footskip}{0.9cm}
+}
+
+%% Standardschrift
+\newcommand{\defaultfont}{\fontfamily{cmss}\fontsize{10pt}{12pt}\fontseries{m}\selectfont}
+\renewcommand{\familydefault}{cmss}
+
+%% Checkboxen
+\newsavebox{\checkedbox}
+\savebox{\checkedbox}(0.2,0.4){
+ \put(-0.15,-0.425){$\times$}
+ \put(-0.15,-0.45){$\Box$}
+}
+\newsavebox{\uncheckedbox}
+\savebox{\uncheckedbox}(0.2,0.4){
+ \put(-0.15,-0.45){$\Box$}
+}
+
+%% Farben
+\definecolor{kivitendoorange}{rgb}{1,0.4,0.2}
+\definecolor{kivitendodarkred}{rgb}{0.49,0,0}
+\definecolor{kivitendoyellow}{rgb}{1,1,0.4}
+\definecolor{kivitendobggray}{gray}{0.9}
+\definecolor{kivitendowhite}{gray}{1}
+
+%% Kopf- und Fußzeilen
+\newcommand{\kivitendofirsthead}{}
+\newcommand{\kivitendofirstfoot}{}
+\newcommand{\kivitendosecondhead}{}
+\newcommand{\kivitendosecondfoot}{\centerline{\defaultfont\small Seite \thepage}}
+
+\newcommand{\myhead}{%
+ \ifthenelse{\boolean{defaultbg}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-28.1){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/hintergrund_seite1.png}}
+ \end{picture}%
+ }{}%
+ \ifthenelse{\boolean{secondpagelogo}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-28.1){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/hintergrund_seite2.png}}
+ \end{picture}%
+ }{}%
+ \ifthenelse{\boolean{draftbg}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-26.9){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/draft.png}}
+ \end{picture}%
+ }{}%
+ \ifthenelse{\boolean{reqspeclogo}}{%
+ \begin{picture}(0,0)
+ \put(3,-22){\includegraphics*[width=13cm,keepaspectratio=true]{images/schachfiguren.jpg}}
+ \put(0.275,-4.1){\colorbox{kivitendoorange}{\begin{minipage}[t][4.5cm]{2.5cm}\hspace*{2.5cm}\end{minipage}}}
+ \put(0.275,-8.8){\colorbox{kivitendodarkred}{\begin{minipage}[t][4.5cm]{2.5cm}\hspace*{2.5cm}\end{minipage}}}
+ \put(0.275,-13.5){\colorbox{kivitendoyellow}{\begin{minipage}[t][4.5cm]{2.5cm}\hspace*{2.5cm}\end{minipage}}}
+ \end{picture}%
+ }{}%
+ \kivitendofirsthead
+}
+
+\newcommand{\mysecondhead}{%
+ \ifthenelse{\boolean{defaultbg} \or \boolean{secondpagelogo}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-28.1){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/hintergrund_seite2.png}}
+ \end{picture}%
+ }{}%
+ \ifthenelse{\boolean{draftbg}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-26.9){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/draft.png}}
+ \end{picture}%
+ }{}%
+ \kivitendosecondhead
+}
+
+\newcommand{\myfoot}{\kivitendofirstfoot}
+\newcommand{\mysecondfoot}{\kivitendosecondfoot}
+
+\renewcommand{\ps@headings}{%
+ \renewcommand{\@oddhead}{\myhead}
+ \renewcommand{\@evenhead}{\@oddhead}%
+ \renewcommand{\@oddfoot}{\myfoot}
+ \renewcommand{\@evenfoot}{\@oddfoot}%
+}
+
+\renewcommand{\ps@plain}{%
+ \renewcommand{\@oddhead}{\mysecondhead}
+ \renewcommand{\@evenhead}{\@oddhead}%
+ \renewcommand{\@oddfoot}{\mysecondfoot}
+ \renewcommand{\@evenfoot}{\@oddfoot}%
+}
+
+\pagestyle{plain}
+\thispagestyle{headings}
+
+% Abschnitte mit Kasten hinterlegt
+
+\newcommand{\reqspecsectionstyle}{%
+\renewcommand{\thesection}{\alph{section}}
+\makeatletter
+\def\section{\@ifstar\unnumberedsection\numberedsection}
+\makeatother
+}
+
+\makeatletter
+\def\numberedsection{\@ifnextchar[%]
+ \numberedsectionwithtwoarguments\numberedsectionwithoneargument}
+\def\unnumberedsection{\@ifnextchar[%]
+ \unnumberedsectionwithtwoarguments\unnumberedsectionwithoneargument}
+\def\numberedsectionwithoneargument#1{\numberedsectionwithtwoarguments[#1]{#1}}
+\def\unnumberedsectionwithoneargument#1{\unnumberedsectionwithtwoarguments[#1]{#1}}
+\def\numberedsectionwithtwoarguments[#1]#2{%
+ \ifhmode\par\fi
+ \removelastskip
+ \vskip 3ex\goodbreak
+ \refstepcounter{section}%
+ \noindent
+ \begingroup
+ \leavevmode\Large\bfseries\raggedright
+ \begin{picture}(0,0)
+ \put(0,0){\colorbox{kivitendoorange}{\parbox{0.7cm}{\hspace*{0.7cm}\\\vspace*{0.2cm}}}}
+ \end{picture}%
+ \hspace*{0.3cm}\textcolor{white}{\thesection{}.}%
+ \quad%
+ #2
+ \par
+ \endgroup
+ \vskip 2ex\nobreak
+ \addcontentsline{toc}{section}{\protect\numberline{\thesection{}.}#1}%
+ }
+\def\unnumberedsectionwithtwoarguments[#1]#2{%
+ \ifhmode\par\fi
+ \removelastskip
+ \vskip 3ex\goodbreak
+ \noindent
+ \begingroup
+ \leavevmode\Large\bfseries\raggedright
+ \leavevmode\Large\bfseries\raggedright
+ #2
+ \par
+ \endgroup
+ \vskip 2ex\nobreak%
+}
+\makeatother
--- /dev/null
+% config: use-template-toolkit=1
+% config: tag-style=$( )$
+$( USE LxLatex )$
+$( USE P )$
+\documentclass{scrartcl}
+
+\usepackage[reqspeclogo,$( IF !rspec.version_id )$draftlogo$( ELSE )$secondpagelogo$( END )$]{kivitendo}
+
+\kivitendobgsettings
+
+\setlength{\LTpre}{0pt}
+\setlength{\LTpost}{0pt}
+
+\renewcommand{\kivitendosecondfoot}{%
+ \parbox{12cm}{%
+ \defaultfont\scriptsize%
+ $( LxLatex.filter(rspec.displayable_name) )$\\
+ $( !rspec.version_id ? "Arbeitskopie ohne Version" : "Version " _ rspec.version.version_number _ " vom " _ rspec.version.itime.to_kivitendo(precision='minute') )$
+
+ \vspace*{0.2cm}%
+ Seite \thepage%
+ }%
+}
+
+\reqspecsectionstyle
+
+\begin{document}
+
+%% Titelseite
+
+\setlongtables
+\defaultfont
+
+\begin{picture}(0,0)
+ \put(3.5,-5){%
+ \begin{minipage}[t][6cm]{12cm}
+ \Large
+ \textcolor{kivitendodarkred}{$( LxLatex.filter(rspec.type.description) )$}
+
+ \huge
+ $( LxLatex.filter(rspec.customer.name) )$
+
+ \vspace*{0.5cm}
+ \Large
+ $( LxLatex.filter(rspec.title) )$
+ \normalsize
+%$( IF rspec.version_id )$
+
+ Version $( LxLatex.filter(rspec.version.version_number) )$
+%$( END )$
+ \end{minipage}%
+ }
+\end{picture}
+
+%% Inhaltsverzeichnis
+
+%\newpage
+
+%\tableofcontents
+
+%% Versionen
+\newpage
+
+\section{Versionen}
+
+\vspace*{0.7cm}
+
+%$( SET working_copy = rspec.working_copy_id ? rspec.working_copy : rspec )$
+%$( SET versioned_copies = rspec.version_id ? working_copy.versioned_copies_sorted(max_version_number = rspec.version.version_number) : working_copy.versioned_copies_sorted )$
+%$( IF !versioned_copies.size )$
+ Bisher wurden noch keine Versionen angelegt.
+%$( ELSE )$
+\begin{longtable}{|p{2cm}|p{2cm}|p{12cm}|}
+ \hline
+ \multicolumn{1}{|r}{\small Version} &
+ \multicolumn{1}{|r|}{\small Datum} &
+ \small Beschreibung\\
+ \hline
+%$( FOREACH versioned_copy = versioned_copies )$
+ \multicolumn{1}{|r}{\small $( LxLatex.filter(versioned_copy.version.version_number) )$} &
+ \multicolumn{1}{|r|}{\small $( LxLatex.filter(versioned_copy.version.itime.to_kivitendo(precision='minute')) )$} &
+ \small $( LxLatex.filter(versioned_copy.version.description) )$\\
+%$( END )$
+ \hline
+\end{longtable}
+%$( END )$
+
+%$( BLOCK text_block_outputter )$
+% $( SET text_blocks = rspec.text_blocks_sorted(output_position=output_position) )$
+% $( IF text_blocks.size )$
+
+ \newpage
+
+ \section{$( heading )$}
+
+% $( FOREACH text_block = text_blocks )$
+
+ \subsection{$( LxLatex.filter(text_block.title) )$}
+
+$( LxLatex.filter(text_block.text) )$
+
+% $( END )$
+% $( END )$
+%$( END )$
+
+%% Textblöcke davor
+$( PROCESS text_block_outputter output_position=0 heading='Allgemeines' )$
+
+%% Abschnitte und Funktionsblöcke
+\newpage
+
+\section{Spezifikation}
+
+\setlength{\LTpre}{-0.3cm}
+
+
+%$( FOREACH top_item = rspec.sections_sorted )$
+
+ \subsection{Abschnitt $( LxLatex.filter(top_item.fb_number) )$: $( LxLatex.filter(top_item.title) )$}
+
+% $( IF top_item.description )$
+ $( LxLatex.filter(top_item.description.replace('\r', '').replace('\n+\Z', '')) )$
+
+ \vspace{0.5cm}
+% $( END )$
+% $( FOREACH item = top_item.children_sorted )$
+\parbox[t]{1.0cm}{\textcolor{kivitendodarkred}{$>>>$}}%
+\parbox[t]{15.0cm}{%
+\begin{longtable}{p{2.8cm}p{11.7cm}}
+ Funktionsblock & $( LxLatex.filter(item.fb_number) )$\\
+ Beschreibung & $( LxLatex.filter(item.description) )$\\
+ Abhängigkeiten & $( LxLatex.filter(P.requirement_spec_item_dependency_list(item)) )$
+\end{longtable}}
+
+% $( FOREACH sub_item = item.children_sorted )$
+\hspace*{1.15cm}\rule{15.2cm}{0.2pt}\\
+\hspace*{1.0cm}%
+\parbox[t]{15.0cm}{%
+\begin{longtable}{p{2.8cm}p{11.7cm}}
+ Unterfunktionsblock & $( LxLatex.filter(sub_item.fb_number) )$\\
+ Beschreibung & $( LxLatex.filter(sub_item.description) )$\\
+ Abhängigkeiten & $( LxLatex.filter(P.requirement_spec_item_dependency_list(sub_item)) )$
+\end{longtable}}
+
+% $( END )$
+
+% $( UNLESS loop.last )$
+\vspace{0.2cm}
+\hrule
+\vspace{0.4cm}
+
+% $( END )$
+
+% $( END )$
+%
+%$( END )$
+
+%% Textblöcke dahinter
+$( PROCESS text_block_outputter output_position=1 heading='Weitere Punkte' )$
+
+
+\end{document}
metadata: { type: "text-blocks-front" },
attr: { id: "tb-front", class: "text-block-context-menu" },
children: [
-[% FOREACH tb = SELF.requirement_spec.text_blocks_for_position(0) %]
+[% FOREACH tb = SELF.requirement_spec.text_blocks_sorted(output_position=0) %]
[% P.requirement_spec_text_block_jstree_data(tb).json %][% IF !loop.last %],[% END %]
[% END %]
]},
metadata: { type: "text-blocks-back" },
attr: { id: "tb-back", class: "text-block-context-menu" },
children: [
-[% FOREACH tb = SELF.requirement_spec.text_blocks_for_position(1) %]
+[% FOREACH tb = SELF.requirement_spec.text_blocks_sorted(output_position=1) %]
[% P.requirement_spec_text_block_jstree_data(tb).json %][% IF !loop.last %],[% END %]
[% END %]
]}];
<div class="sub-function-block-header" id="sub-function-block_header_[%- requirement_spec_item.id -%]">
[%- LxERP.t8("Sub function blocks") -%]
</div>
- [%- FOREACH sub_function_block = requirement_spec_item.sorted_children -%]
+ [%- FOREACH sub_function_block = requirement_spec_item.children_sorted -%]
[%- INCLUDE 'requirement_spec_item/_sub_function_block.html' requirement_spec_item=sub_function_block -%]
[%- END -%]
</div>
</div>
<div id="section-list" class="section">
- [%- FOREACH function_block = requirement_spec_item.sorted_children -%]
+ [%- FOREACH function_block = requirement_spec_item.children_sorted -%]
[%- INCLUDE 'requirement_spec_item/_function_block.html' requirement_spec_item=function_block -%]
[%- END -%]
</div>