#!/usr/bin/perl
+our $partner = "cyclofficine_ivry";
-our $VERSION = '0.0.1';
+our $VERSION = '2013.10.27';
use strict;
use warnings FATAL => qw(all);
use utf8;
require XML::Generator;
require Text::Trim;
-sub parse_date ($) {
- my ($_) = @_;
- if ($_) {
- my ($jj,$mm,undef,$yy) = ($_ =~ m{^\s*([0-3]?[0-9])\s*/\s*([0-1]?[0-9])\s*/\s*(20)?([0-9][0-9])\s*$});
- return "20$yy-$mm-$jj";
- }
- else { return undef };
+sub parse_date (@) {
+ ($_) = @_;
+ my ($jj,$mm,undef,$yy) = ($_ =~ m{^\s*([0-3]?[0-9])\s*/\s*([0-1]?[0-9])\s*/\s*(20)?([0-9][0-9])\s*$});
+ return "20$yy-$mm-$jj";
}
-sub parse_euro ($) {
+sub parse_amount (@) {
my ($_) = @_;
- if ($_) {
- my ($num) = ($_ =~ m{^\s*([0-9]+),00\s*€?.$});
- return $num;
+ my %amounts =
+ ( "gratuit" => 0
+ , "Gratuit" => 0
+ , "offert" => 0
+ , "Offert" => 0
+ , "?" => 0
+ );
+ $_ = Text::Trim::trim($_);
+ if (exists $amounts{$_}) {
+ return $amounts{$_};
+ }
+ else {
+ ($_) = ($_ =~ m{^\s*([0-9]+),00\s*€?.$});
+ return $_;
}
- else { return undef };
}
-sub parse_line (@) {
- my ($pass, $xml, $number_next, $adh, $data, $data_noupdate, $data_rejected, $data_reparse) = @_;
- my ($number, $date_cot1, $cot1, $moyen_cot1, $reduction, $genre, $prenom, $nom, $naissance
- , $email, $tel_fixe, $tel_mobile, $addr_postale, $cp, $city, $comment) =
- #map {Text::Trim::trim($_)}
- @$adh;
- my $partner = "cyclofficine_ivry";
- $number = Text::Trim::trim($number);
- if (not ($number =~ m/^[0-9]+$/)) {
- if ($pass == 0) {
- push @$data_reparse, $adh;
- return ($number_next);
- }
- else {
- $comment
- =($comment?"$comment. ":"")
- ."(n° malformé d'origine : $number)";
- $number = "$number_next";
- }
+sub parse_payment_mean (@) {
+ ($_) = @_;
+ my %payment_means =
+ ( "Espèces" => "cash"
+ , "Offert" => "cash"
+ , "Chèque" => "bank"
+ );
+ $_ = Text::Trim::trim($_);
+ return exists $payment_means{$_}
+ ? $payment_means{$_}
+ : "cash";
+ }
+sub parse_discount ($) {
+ ($_) = @_;
+ my %discounts =
+ ( "Chômeur" => "unemployed"
+ , "Chmeur" => "unemployed"
+ , "Atelier vélo IdF" => "velorution_idf"
+ , "Étudiant" => "student"
+ , "Etudiant" => "student"
+ , "etudiant" => "student"
+ , "Retraité" => "retired"
+ );
+ $_ = Text::Trim::trim($_);
+ return exists $discounts{$_}
+ ? $discounts{$_}
+ : "standard";
+ }
+sub parse_gender (@) {
+ ($_) = @_;
+ my %genders =
+ ( "Ass." => "association"
+ , "M." => "male"
+ , "Mme" => "female"
+ , "Mme." => "female"
+ );
+ $_ = Text::Trim::trim($_);
+ return exists $genders{$_}
+ ? $genders{$_}
+ : undef;
+ }
+
+our $last_number = -1;
+our $greatest_number = -1;
+sub member_of_csv_line (@) {
+ my ($members, $csv_line, $csv_lines_rejected, $csv_lines_to_reparse) = @_;
+ print STDERR ("member_of_csv_line: csv_line=".Data::Dumper::Dumper($csv_line));
+ my $number = Text::Trim::trim($csv_line->{number});
+ if (not $number) {
+ push @$csv_lines_rejected, $csv_line;
}
- if ($number =~ m/^[0-9]+$/) {
- if ($nom or $email) {
- $number = $number + 0;
- $number_next = $number + 1;
- $date_cot1 = parse_date($date_cot1);
- #$date_cot2 = parse_date($date_cot2);
- $cot1 = parse_euro($cot1);
- #$cot2 = parse_euro($cot2);
- my $city = ($cp and ($cp =~ m/^750[0-2][0-9]$/) ? "Paris" : undef);
- my $country = ($cp and ($cp =~ m/^UK$/) ? "Royaume-Uni" : "France");
- my $phone = undef;
- my $street = undef;
- my %reductions =
- ( "Chômeur" => "unemployed"
- , "Chmeur" => "unemployed"
- , "Atelier vélo IdF" => "velorution_idf"
- , "Étudiant" => "student"
- , "Etudiant" => "student"
- , "etudiant" => "student"
- , "Retraité" => "retired"
- );
- $reduction = Text::Trim::trim($reduction);
- $reduction
- = exists $reductions{$reduction}
- ? $reductions{$reduction}
- : "standard";
- my %pay_accounts =
- ( "Espèces" => "cash"
- , "Chèque" => "bank"
- );
- my $pay_account_cot1 = Text::Trim::trim($moyen_cot1);
- $pay_account_cot1
- = exists $pay_accounts{$pay_account_cot1}
- ? $pay_accounts{$pay_account_cot1}
- : "cash";
- push @$data, $xml->record
- ( { id => "res_partner_${partner}_$number"
- , model => "res.partner"
+ else {
+ my $member = {};
+ if (not $number or not ($number =~ m/^[0-9]+$/)) {
+ if (defined $csv_lines_to_reparse) {
+ push @$csv_lines_to_reparse, $csv_line;
+ return;
+ }
+ else {
+ $greatest_number = $greatest_number + 1;
+ print STDERR "WARNING: renumérotation: ".($number?$number:"undef")." -> $greatest_number\n";
+ $csv_line->{comment}
+ =($csv_line->{comment}?"$csv_line->{comment}. ":"")
+ ."(n° malformé d'origine : ".($number?$number:"undef").")";
+ $number = "$greatest_number";
+ }
+ }
+ if ($number =~ m/^[0-9]+$/) {
+ $number = $number + 0;
+ if ($last_number + 1 != $number + 0) {
+ print STDERR "WARNING: discontinuité: attendu=".($last_number + 1)." eu=".($number + 0)."\n";
+ }
+ $last_number = $number;
+ $greatest_number = $number
+ if $number > $greatest_number;
+
+ if ($csv_line->{name} or $csv_line->{email}) {
+ if (exists $members->{$number}) {
+ $member = $members->{$number};
}
- , $xml->field({name => "name"}, $nom . ($prenom ? " $prenom" : ""))
- , $xml->field({name => "member_ident"}, $number)
- , $xml->field({name => "type"}, "default")
- , ($cp ? $xml->field({name => "zip"}, $cp) : ())
- , ($city ? $xml->field({name => "city"}, $city) : ())
- , ($country ? $xml->field({name => "country_id", model => "res.country", search => "[('name','=','$country')]"}) : ())
- , ($email ? $xml->field({name => "email"}, $email) : ())
- , ($phone ? $xml->field({name => "phone"}) : ())
- , ($addr_postale? $xml->field({name => "street"}, $addr_postale) : ())
- , ($comment ? $xml->field({name => "comment"}, $comment) : ())
- );
- my %cots =
- ( ($cot1 ? ($cot1 => $date_cot1) : ())
- #, ($cot2 ? ($cot2 => $date_cot2) : ())
- ) ;
- while ( my ($amount, $date_from) = each(%cots) ) {
- push @$data_noupdate, $xml->function
- ( { model => "account.invoice"
- , name => "pay_and_reconcile"
- }
- , $xml->xmlcmnt('ids')
- , $xml->function
- ( { model => "account.invoice"
- , name => "draft2open"
- }
- , $xml->function
- ( { model => "res.partner"
- , name => "create_membership_invoice"
- }
- , $xml->xmlcmnt('partner_id')
- , $xml->value({eval => "ref('res_partner_${partner}_$number')"})
- , $xml->xmlcmnt('product_id')
- , $xml->value({eval => "ref('product_${reduction}_member')"})
- , $xml->xmlcmnt('context')
- , $xml->value({eval => "{'amount':$amount, 'date_from':'$date_from'}"})
- )
- )
- , $xml->xmlcmnt('pay_amount')
- , $xml->value ({eval => "$amount"})
- , $xml->xmlcmnt('pay_account_id')
- , $xml->value ({model => "account.account", search => "[('name', '=', 'Cash')]"})
- , $xml->xmlcmnt("moyen_cot1: $moyen_cot1")
- , $xml->xmlcmnt('period_id')
- , $xml->value ({model => "account.period", search => "[('name', '=', time.strftime('%m/%Y'))]"})
- , $xml->xmlcmnt('pay_journal_id')
- , $xml->value ({model => "account.journal", search => "[('name', '=', 'Cash')]"})
- , $xml->xmlcmnt('writeoff_acc_id')
- , $xml->value ({model => "account.account", search => "[('name', '=', 'Cash')]"})
- , $xml->xmlcmnt('writeoff_period_id')
- , $xml->value ({model => "account.period", search => "[('name', '=', time.strftime('%m/%Y'))]"})
- , $xml->xmlcmnt('writeoff_journal_id')
- , $xml->value ({model => "account.journal", search => "[('name', '=', 'Cash')]"})
- , $xml->xmlcmnt('context')
- , $xml->value ({eval => "{}"})
- , $xml->xmlcmnt('name')
- , $xml->value ({eval => "str('Import de paiement automatique')"})
- );
+ else {
+ $member = {};
+ $members->{$number} = $member;
+ }
+ $member->{number} = $number;
+ $member->{name}
+ = $csv_line->{name}
+ unless $member->{name};
+ $member->{firstname}
+ = $csv_line->{firstname}
+ unless $member->{firstname};
+ $member->{email}
+ = $csv_line->{email}
+ unless $member->{email};
+ $member->{cotisations}
+ = []
+ unless exists $member->{cotisations};
+ push @{$member->{cotisations}},
+ { amount => parse_amount($csv_line->{cotisation_amount})
+ , date => parse_date($csv_line->{cotisation_date})
+ , discount => parse_discount($csv_line->{cotisation_discount})
+ , mean => parse_payment_mean($csv_line->{cotisation_mean})
+ };
+ $member->{zip}
+ =($csv_line->{zip}
+ ? $csv_line->{zip}
+ : undef)
+ unless $member->{zip};
+ $member->{gender}
+ =($csv_line->{gender}
+ ? parse_gender($csv_line->{gender})
+ : undef)
+ unless $member->{gender};
+ $member->{city}
+ =($csv_line->{city}
+ ? $csv_line->{city}
+ : ($member->{zip} and ($member->{zip} =~ m/^750[0-2][0-9]$/) ? "Paris" : undef))
+ unless $member->{city};
+ $member->{country}
+ = ($member->{zip} and ($member->{zip} =~ m/^UK$/)
+ ? "Royaume-Uni"
+ : "France")
+ unless $member->{country};
+ $member->{phone}
+ =($csv_line->{landline_phone}
+ ? $csv_line->{landline_phone}
+ :($csv_line->{mobile_phone}
+ ? $csv_line->{mobile_phone}
+ : undef))
+ unless $member->{phone};
+ $member->{street}
+ =($csv_line->{street}
+ ? $csv_line->{street}
+ : undef)
+ unless $member->{street};
+ $member->{comment}
+ =($csv_line->{comment}
+ ? $csv_line->{comment}
+ : undef)
+ unless $member->{comment};
+ }
+ else {
+ push @$csv_lines_rejected, $csv_line;
}
}
- else {
- push @$data_rejected, $adh;
+ }
+ }
+sub xml_of_member (@) {
+ my ($xml, $member, $xml_data, $xml_data_noupdate) = @_;
+ print STDERR ("xml_of_member: member=".Data::Dumper::Dumper($member));
+ push @$xml_data, $xml->record
+ ( { id => "res_partner_${partner}_".$member->{number}
+ , model => "res.partner"
}
+ , $xml->field({name => "name"}, $member->{name} . ($member->{firstname} ? " ".$member->{firstname} : ""))
+ , $xml->field({name => "member_ident"}, $member->{number})
+ , $xml->field({name => "type"}, "default")
+ , ($member->{zip} ? $xml->field({name => "zip"}, $member->{zip}) : ())
+ , ($member->{city} ? $xml->field({name => "city"}, $member->{city}) : ())
+ , ($member->{country}? $xml->field({name => "country_id", model => "res.country", search => "[('name','=','".$member->{country}."')]"}) : ())
+ , ($member->{email} ? $xml->field({name => "email"}, $member->{email}) : ())
+ , ($member->{phone} ? $xml->field({name => "phone"}, $member->{phone}) : ())
+ , ($member->{street} ? $xml->field({name => "street"}, $member->{street}) : ())
+ , ($member->{comment}? $xml->field({name => "comment"}, $member->{comment}) : ())
+ );
+ die unless $member->{cotisations};
+ foreach my $cotisation (@{$member->{cotisations}}) {
+ push @$xml_data_noupdate, $xml->function
+ ( { model => "account.invoice"
+ , name => "pay_and_reconcile"
+ }
+ , $xml->xmlcmnt('ids')
+ , $xml->function
+ ( { model => "account.invoice"
+ , name => "draft2open"
+ }
+ , $xml->function
+ ( { model => "res.partner"
+ , name => "create_membership_invoice"
+ }
+ , $xml->xmlcmnt('partner_id')
+ , $xml->value({eval => "ref('res_partner_${partner}_".$member->{number}."')"})
+ , $xml->xmlcmnt('product_id')
+ , $xml->value({eval => "ref('product_".$cotisation->{discount}."_member')"})
+ , $xml->xmlcmnt('context')
+ , $xml->value({eval => "{'amount':".$cotisation->{amount}.", 'date_from':'".$cotisation->{date}."'}"})
+ )
+ )
+ , $xml->xmlcmnt('pay_amount')
+ , $xml->value ({eval => "$cotisation->{amount}"})
+ , $xml->xmlcmnt('pay_account_id')
+ , $xml->value ({model => "account.account", search => "[('name', '=', 'Cash')]"})
+ , $xml->xmlcmnt("mean: $cotisation->{mean}")
+ , $xml->xmlcmnt('period_id')
+ , $xml->value ({model => "account.period", search => "[('name', '=', time.strftime('%m/%Y'))]"})
+ , $xml->xmlcmnt('pay_journal_id')
+ , $xml->value ({model => "account.journal", search => "[('name', '=', 'Cash')]"})
+ , $xml->xmlcmnt('writeoff_acc_id')
+ , $xml->value ({model => "account.account", search => "[('name', '=', 'Cash')]"})
+ , $xml->xmlcmnt('writeoff_period_id')
+ , $xml->value ({model => "account.period", search => "[('name', '=', time.strftime('%m/%Y'))]"})
+ , $xml->xmlcmnt('writeoff_journal_id')
+ , $xml->value ({model => "account.journal", search => "[('name', '=', 'Cash')]"})
+ , $xml->xmlcmnt('context')
+ , $xml->value ({eval => "{}"})
+ , $xml->xmlcmnt('name')
+ , $xml->value ({eval => "str('Import de paiement automatique')"})
+ );
}
- return ($number_next);
}
sub main () {
my $csv = Text::CSV->new
- ({ binary => 1, eol => $/
+ ({binary => 1
+ , eol => $/
, sep_char => ';'
});
my $xml = XML::Generator->new
, pretty => 2
);
my $in = IO::Wrap::wraphandle(\*STDIN);
+
my $csv_head = $csv->getline($in);
#print STDERR ("head: ", join("|", @$csv_head), "\n");
- $csv->column_names(@$csv_head);
- my $data = [];
- my $data_noupdate = [];
- my $data_rejected = [];
- my $data_reparse = [];
- my $number_next = 1;
- while (my $adh = $csv->getline($in)) {
- #print STDERR ("line: ", join("|", @$adh), "\n");
- ($number_next) = parse_line(0, $xml, $number_next, $adh, $data, $data_noupdate, $data_rejected, $data_reparse);
+ #$csv->column_names(@$csv_head);
+ $csv->column_names (qw (
+ number
+ cotisation_date
+ cotisation_amount
+ cotisation_mean
+ cotisation_discount
+ gender
+ firstname
+ name
+ birth
+ email
+ landline_phone
+ mobile_phone
+ street
+ zip
+ city
+ comment
+ ));
+ my $xml_data = [];
+ my $xml_data_noupdate = [];
+ my $csv_lines_rejected = [];
+ my $csv_lines_to_reparse = [];
+ my $members = {};
+ while (my $csv_line = $csv->getline_hr($in)) {
+ #print STDERR ("csv_line: ", join("|", @$csv_line), "\n");
+ member_of_csv_line($members, $csv_line, $csv_lines_rejected, $csv_lines_to_reparse);
+ }
+ print STDERR "csv_lines_to_reparse=".Data::Dumper::Dumper($csv_lines_to_reparse);
+ foreach my $csv_line (@$csv_lines_to_reparse) {
+ member_of_csv_line($members, $csv_line, $csv_lines_rejected, undef);
}
- foreach (@$data_reparse) {
- ($number_next) = parse_line(1, $xml, $number_next, $_, $data, $data_noupdate, $data_rejected, []);
+ foreach my $number (sort {$a <=> $b} (keys %$members)) {
+ xml_of_member($xml, $members->{$number}, $xml_data, $xml_data_noupdate);
}
- push @$data_noupdate, $xml->record
+ push @$xml_data_noupdate, $xml->record
( { id => "remembership.member_ident_sequence"
, model => "ir.sequence"
}
- , $xml->field({name => "number_next"}, $number_next)
+ , $xml->field({name => "number_next"}, $greatest_number + 1)
);
binmode STDOUT, ':utf8';
print $xml->openerp
- ( $xml->data(@$data)
+ ( $xml->data(@$xml_data)
, $xml->data
- ( {noupdate=>"1"}
- , @$data_noupdate )
+ ( {noupdate => "1"}
+ , @$xml_data_noupdate )
);
my $out = IO::Wrap::wraphandle(\*STDERR);
- foreach (@$data_rejected) {
- $csv->print($out, $_);
- }
+ print STDERR "csv_lines_rejected=".Data::Dumper::Dumper($csv_lines_rejected);
}
main;