Adding initial project.
This commit is contained in:
parent
73b06c3a98
commit
2f40bb45ea
19
Build.PL
Executable file
19
Build.PL
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
use Module::Build;
|
||||||
|
|
||||||
|
my $home = $ENV{HOME};
|
||||||
|
|
||||||
|
my $build = Module::Build->new(
|
||||||
|
module_name => 'Owlcode::Tech::Facturer',
|
||||||
|
license => 'AGPLv3',
|
||||||
|
dist_author => 'Sergio Iglesias <contact@owlcode.tech>',
|
||||||
|
dist_abstract => 'Make a spanish facture.',
|
||||||
|
requires => {
|
||||||
|
'Mojolicious' => 0,
|
||||||
|
'Moo' => 0,
|
||||||
|
'Perl::Tidy' => 0,
|
||||||
|
'Path::Tiny' => 0,
|
||||||
|
'DateTime' => 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
$build->create_build_script;
|
6
config.example.json
Normal file
6
config.example.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"fullname": "Pepito el de los Palotes",
|
||||||
|
"dni": "12345678A",
|
||||||
|
"address": "Calle Salvador Allende S/N",
|
||||||
|
"postal_code": "13243"
|
||||||
|
}
|
16
example_bill.sh
Normal file
16
example_bill.sh
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
(
|
||||||
|
echo '8'
|
||||||
|
echo 'y'
|
||||||
|
echo '2'
|
||||||
|
echo 'Example concept'
|
||||||
|
echo '1'
|
||||||
|
echo '20.03'
|
||||||
|
echo 'y'
|
||||||
|
echo 'Example concept 2'
|
||||||
|
echo '2'
|
||||||
|
echo '20.03'
|
||||||
|
echo 'n'
|
||||||
|
) | perl -Ilib scripts/facturer.pl
|
||||||
|
|
45
lib/Owlcode/Tech/Facturer.pm
Normal file
45
lib/Owlcode/Tech/Facturer.pm
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package Owlcode::Tech::Facturer;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use Moo;
|
||||||
|
use Path::Tiny;
|
||||||
|
|
||||||
|
use Mojo::JSON qw(decode_json encode_json);
|
||||||
|
|
||||||
|
has config => ( is => 'lazy' );
|
||||||
|
|
||||||
|
my $ROOT_PATH = path(__FILE__)->parent->parent->parent->parent->realpath;
|
||||||
|
|
||||||
|
sub fullname ($self) {
|
||||||
|
return $self->config->{fullname};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dni ($self) {
|
||||||
|
return $self->config->{dni};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub address ($self) {
|
||||||
|
return $self->config->{address};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub postal_code ($self) {
|
||||||
|
return $self->config->{postal_code};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _build_config {
|
||||||
|
my $config_path = $ROOT_PATH->child('config.json');
|
||||||
|
return decode_json( $config_path->slurp_utf8 );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub run ($self) {
|
||||||
|
require Owlcode::Tech::Facturer::MainCommand;
|
||||||
|
my $main_command = Owlcode::Tech::Facturer::MainCommand->new;
|
||||||
|
$main_command->run;
|
||||||
|
}
|
||||||
|
1;
|
352
lib/Owlcode/Tech/Facturer/MainCommand.pm
Normal file
352
lib/Owlcode/Tech/Facturer/MainCommand.pm
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
package Owlcode::Tech::Facturer::MainCommand;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use Data::Dumper;
|
||||||
|
|
||||||
|
use Moo;
|
||||||
|
use DateTime;
|
||||||
|
use Mojo::Template;
|
||||||
|
use Path::Tiny;
|
||||||
|
|
||||||
|
use Owlcode::Tech::Facturer;
|
||||||
|
|
||||||
|
my $SIMPLIFICADA = 1;
|
||||||
|
my $COMPLETA = 2;
|
||||||
|
|
||||||
|
my $IVA_INCLUDED = 1;
|
||||||
|
my $IVA_NOT_INCLUDED = 2;
|
||||||
|
|
||||||
|
binmode STDOUT, ':utf8';
|
||||||
|
|
||||||
|
sub run ($self) {
|
||||||
|
my $app = Owlcode::Tech::Facturer->new;
|
||||||
|
|
||||||
|
my $self_name = $app->fullname;
|
||||||
|
my $self_dni = $app->dni;
|
||||||
|
my $self_address = $app->address;
|
||||||
|
my $self_postal_code = $app->postal_code;
|
||||||
|
my $client_dni;
|
||||||
|
my $client_name;
|
||||||
|
|
||||||
|
say "Tu nombre completo es $self_name.";
|
||||||
|
say "Tu dni es $self_dni.";
|
||||||
|
say "Tu dirección es $self_address.";
|
||||||
|
say "Tu código postal es $self_postal_code.";
|
||||||
|
|
||||||
|
my $number = $self->ask_bill_number;
|
||||||
|
say "Creando factura $number.";
|
||||||
|
my $date = $self->ask_for_date;
|
||||||
|
say "Escogida fecha: @{[$self->print_date($date)]}.";
|
||||||
|
my $kind_of_bill = $self->ask_kind_of_bill;
|
||||||
|
say $kind_of_bill;
|
||||||
|
if ( $kind_of_bill eq $COMPLETA ) {
|
||||||
|
$client_dni = $self->ask_for_client_dni;
|
||||||
|
$client_name = $self->ask_for_client_name;
|
||||||
|
}
|
||||||
|
my $concepts = $self->ask_for_concepts;
|
||||||
|
print 'Los conceptos son: ' . Data::Dumper::Dumper $concepts;
|
||||||
|
|
||||||
|
my $final_data = $self->final_data($concepts);
|
||||||
|
my $mt = Mojo::Template->new( auto_escape => 1 );
|
||||||
|
path("FACTURA @{[sprintf '%06d', $number]}.html")->spew_utf8(
|
||||||
|
$mt->vars(1)->render(
|
||||||
|
$self->template_bill,
|
||||||
|
{
|
||||||
|
self_name => $self_name,
|
||||||
|
self_dni => $self_dni,
|
||||||
|
self_address => $self_address,
|
||||||
|
self_postal_code => $self_postal_code,
|
||||||
|
client_dni => $client_dni,
|
||||||
|
client_name => $client_name,
|
||||||
|
number => $number,
|
||||||
|
date => $self->print_date($date),
|
||||||
|
kind_of_bill => $kind_of_bill,
|
||||||
|
SIMPLIFICADA => $SIMPLIFICADA,
|
||||||
|
COMPLETA => $COMPLETA,
|
||||||
|
concepts => $concepts,
|
||||||
|
final_data => $final_data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub template_bill ($self) {
|
||||||
|
my $text = <<'EOF';
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Factura número <%= sprintf '%06d', $number %></h1>
|
||||||
|
|
||||||
|
<h2>Datos emisor.</h2>
|
||||||
|
|
||||||
|
<p>Emitida por <%= $self_name %> con DNI/CIF <%= $self_dni %> en fecha <%= $date %>.</p>
|
||||||
|
% if ($kind_of_bill eq $COMPLETA) {
|
||||||
|
<h2>Datos receptor.</h2>
|
||||||
|
<p>Destinatario <%= $client_name %> con DNI/CIF <%= $client_dni %>.</p>
|
||||||
|
% }
|
||||||
|
<h2>Desglose factura.</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Concepto</th>
|
||||||
|
<th>Base</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
% for my $concept (@$concepts) {
|
||||||
|
<tr>
|
||||||
|
<td><%=$concept->{title}%></td>
|
||||||
|
<td><%=sprintf "%.02f", $concept->{base}%>€</td>
|
||||||
|
<td><%=sprintf "%.02f", $concept->{total}%>€</td>
|
||||||
|
</tr>
|
||||||
|
% }
|
||||||
|
</table>
|
||||||
|
<h2>Precio Final.</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Base.</th>
|
||||||
|
<th>%IVA.</th>
|
||||||
|
<th>Cuota IVA.</th>
|
||||||
|
<th>Total.</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><%=$final_data->{base}%>€</td>
|
||||||
|
<td>21%</td>
|
||||||
|
<td><%=$final_data->{iva}%>€</td>
|
||||||
|
<td><%=$final_data->{total}%>€</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub final_data ( $self, $concepts ) {
|
||||||
|
my $final_total = 0;
|
||||||
|
my $final_base = 0;
|
||||||
|
my $final_iva = 0;
|
||||||
|
for my $concept (@$concepts) {
|
||||||
|
my $base = $concept->{base};
|
||||||
|
my $total = $concept->{total};
|
||||||
|
$final_total += $total;
|
||||||
|
$final_base += $base;
|
||||||
|
}
|
||||||
|
$final_base = sprintf( "%.02f", $final_base );
|
||||||
|
$final_total = sprintf( "%.02f", $final_total );
|
||||||
|
$final_iva = $final_total - $final_base;
|
||||||
|
return {
|
||||||
|
iva => $final_iva,
|
||||||
|
total => $final_total,
|
||||||
|
base => $final_base,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_for_client_name ($self) {
|
||||||
|
my $input;
|
||||||
|
while ( !defined $input || $input =~ /^\s*$/ ) {
|
||||||
|
print "Introduce el nombre completo del cliente o su empresa
|
||||||
|
> ";
|
||||||
|
$input = <STDIN>;
|
||||||
|
chomp $input;
|
||||||
|
}
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_for_client_dni ($self) {
|
||||||
|
my $input;
|
||||||
|
while ( !defined $input || $input =~ /^\s*$/ ) {
|
||||||
|
print "Introduce DNI/CIF/NIE. (No se validará, ten cuidado.)
|
||||||
|
> ";
|
||||||
|
$input = <STDIN>;
|
||||||
|
chomp $input;
|
||||||
|
}
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_one_concept ($self) {
|
||||||
|
my $title = $self->ask_concept_title;
|
||||||
|
my $total = $self->ask_concept_total;
|
||||||
|
my $base = $total / 1.21;
|
||||||
|
return {
|
||||||
|
title => $title,
|
||||||
|
total => $total,
|
||||||
|
base => $base,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_concept_total ($self) {
|
||||||
|
my $has_iva = $self->ask_concept_has_iva_included;
|
||||||
|
my $input;
|
||||||
|
while ( !defined $input || $input !~ /^[0-9]+(?:\.[0-9]+)?$/ ) {
|
||||||
|
|
||||||
|
print "Introduce precio " . ( $has_iva ? 'con' : 'sin' ) . " IVA:
|
||||||
|
> ";
|
||||||
|
$input = <STDIN>;
|
||||||
|
chomp $input;
|
||||||
|
}
|
||||||
|
if ($has_iva) {
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
return $input * 1.21;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_concept_has_iva_included ($self) {
|
||||||
|
my $input;
|
||||||
|
while ( !defined $input
|
||||||
|
|| $input ne $IVA_INCLUDED
|
||||||
|
&& $input ne $IVA_NOT_INCLUDED
|
||||||
|
&& $input ne '' )
|
||||||
|
{
|
||||||
|
print "Escoge modalidad de IVA en el precio que vas a introducir:
|
||||||
|
($IVA_INCLUDED) IVA incluido.
|
||||||
|
($IVA_NOT_INCLUDED) IVA NO incluido.
|
||||||
|
> ";
|
||||||
|
$input = <STDIN>;
|
||||||
|
chomp $input;
|
||||||
|
}
|
||||||
|
if ( $input eq $IVA_INCLUDED ) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_concept_title ($self) {
|
||||||
|
my $input;
|
||||||
|
if ( !defined $input || $input =~ /^\s*$/ ) {
|
||||||
|
print "Introduce el concepto de la factura:
|
||||||
|
> ";
|
||||||
|
$input = <STDIN>;
|
||||||
|
chomp $input;
|
||||||
|
}
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_for_concepts ($self) {
|
||||||
|
my @concepts;
|
||||||
|
push @concepts, $self->ask_one_concept;
|
||||||
|
while ( $self->ask_do_want_more_concepts ) {
|
||||||
|
push @concepts, $self->ask_one_concept;
|
||||||
|
}
|
||||||
|
return [@concepts];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_do_want_more_concepts ($self) {
|
||||||
|
my $input;
|
||||||
|
while ( !defined $input
|
||||||
|
|| $input ne '' && lc($input) ne 'y' && lc($input) ne 'n' )
|
||||||
|
{
|
||||||
|
print "¿Quieres introducir más conceptos? [n]
|
||||||
|
> ";
|
||||||
|
$input = <STDIN>;
|
||||||
|
chomp $input;
|
||||||
|
}
|
||||||
|
if ( lc($input) eq 'y' ) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_for_date ($self) {
|
||||||
|
my $date = DateTime->now;
|
||||||
|
my $user_response;
|
||||||
|
|
||||||
|
while ( !defined $user_response
|
||||||
|
|| $user_response ne ''
|
||||||
|
&& lc($user_response) ne 'y'
|
||||||
|
&& lc($user_response) ne 'n' )
|
||||||
|
{
|
||||||
|
print "¿Quieres continuar con la fecha actual? "
|
||||||
|
. " @{[$self->print_date($date)]} [Y]
|
||||||
|
> ";
|
||||||
|
$user_response = <STDIN>;
|
||||||
|
chomp $user_response;
|
||||||
|
}
|
||||||
|
if ( $user_response eq '' || lc($user_response) eq 'y' ) {
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
|
$self->ask_year($date);
|
||||||
|
$self->ask_month($date);
|
||||||
|
$self->ask_day($date);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_day ( $self, $date ) {
|
||||||
|
my $day;
|
||||||
|
while ( !defined $day || $day ne '' && $day !~ /^[0-9]+$/ ) {
|
||||||
|
print "Escoge un día. [@{[$date->day]}]
|
||||||
|
> ";
|
||||||
|
$day = <STDIN>;
|
||||||
|
chomp $day;
|
||||||
|
}
|
||||||
|
if ( $day eq '' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$date->set_day($day);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_month ( $self, $date ) {
|
||||||
|
my $month;
|
||||||
|
while ( !defined $month || $month ne '' && $month !~ /^[0-9]+$/ ) {
|
||||||
|
print "Escoge un mes. [@{[$date->month]}]
|
||||||
|
> ";
|
||||||
|
$month = <STDIN>;
|
||||||
|
chomp $month;
|
||||||
|
}
|
||||||
|
if ( $month eq '' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$date->set_month($month);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_year ( $self, $date ) {
|
||||||
|
my $year;
|
||||||
|
while ( !defined $year || $year ne '' && $year !~ /^[0-9]+$/ ) {
|
||||||
|
print "Escoge un año. [@{[$date->year]}]
|
||||||
|
> ";
|
||||||
|
$year = <STDIN>;
|
||||||
|
chomp $year;
|
||||||
|
}
|
||||||
|
if ( $year eq '' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$date->set_year($year);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_date ( $self, $date ) {
|
||||||
|
return $date->strftime('%F');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_kind_of_bill ($self) {
|
||||||
|
my $kind_of_bill;
|
||||||
|
while ( !defined $kind_of_bill
|
||||||
|
|| $kind_of_bill ne $SIMPLIFICADA && $kind_of_bill ne $COMPLETA )
|
||||||
|
{
|
||||||
|
print "¿Qué tipo de factura quieres crear?
|
||||||
|
($SIMPLIFICADA) Simplificada.
|
||||||
|
($COMPLETA) Completa.
|
||||||
|
> ";
|
||||||
|
$kind_of_bill = <STDIN>;
|
||||||
|
chomp $kind_of_bill;
|
||||||
|
}
|
||||||
|
return $kind_of_bill;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask_bill_number ($self) {
|
||||||
|
my $number;
|
||||||
|
while ( !defined $number || $number !~ /^[0-9]+$/ ) {
|
||||||
|
print "¿Cual es el número de factura?
|
||||||
|
> ";
|
||||||
|
$number = <STDIN>;
|
||||||
|
chomp $number;
|
||||||
|
}
|
||||||
|
return $number;
|
||||||
|
}
|
||||||
|
1;
|
11
scripts/facturer.pl
Normal file
11
scripts/facturer.pl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
use v5.36.0;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use Owlcode::Tech::Facturer;
|
||||||
|
|
||||||
|
Owlcode::Tech::Facturer->new->run;
|
Loading…
Reference in New Issue
Block a user