#!/usr/bin/perl our $partner = "cyclofficine_ivry"; our $VERSION = '2013.10.27'; use strict; use warnings FATAL => qw(all); use utf8; use open qw/:std :utf8/; require Data::Dumper; require Encode; require IO::Wrap; require Text::CSV; #require Text::CSV::Encoded; require XML::Generator; require Text::Trim; 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_amount (@) { my ($_) = @_; 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 $_; } } 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; } 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}; } 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; } } } } 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')"}) ); } } sub main () { my $csv = Text::CSV->new ({binary => 1 , eol => $/ , sep_char => ';' }); my $xml = XML::Generator->new ( escape => 'always' , conformance => 'strict' , empty => 'self' , 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); $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 my $number (sort {$a <=> $b} (keys %$members)) { xml_of_member($xml, $members->{$number}, $xml_data, $xml_data_noupdate); } push @$xml_data_noupdate, $xml->record ( { id => "remembership.member_ident_sequence" , model => "ir.sequence" } , $xml->field({name => "number_next"}, $greatest_number + 1) ); binmode STDOUT, ':utf8'; print $xml->openerp ( $xml->data(@$xml_data) , $xml->data ( {noupdate => "1"} , @$xml_data_noupdate ) ); my $out = IO::Wrap::wraphandle(\*STDERR); print STDERR "csv_lines_rejected=".Data::Dumper::Dumper($csv_lines_rejected); } main;