--- /dev/null
+package SL::DB::Helper::ALL;
+
+use strict;
+
+use SL::DB::AccTrans;
+use SL::DB::AccTransaction;
+use SL::DB::Assembly;
+use SL::DB::AuditTrail;
+use SL::DB::BankAccount;
+use SL::DB::Bin;
+use SL::DB::Buchungsgruppe;
+use SL::DB::Business;
+use SL::DB::Chart;
+use SL::DB::Contact;
+use SL::DB::CustomVariable;
+use SL::DB::CustomVariableConfig;
+use SL::DB::CustomVariableValidity;
+use SL::DB::Customer;
+use SL::DB::CustomerTax;
+use SL::DB::Datev;
+use SL::DB::Default;
+use SL::DB::DeliveryOrder;
+use SL::DB::DeliveryOrderItem;
+use SL::DB::DeliveryOrderItemsStock;
+use SL::DB::Department;
+use SL::DB::DptTrans;
+use SL::DB::Draft;
+use SL::DB::Dunning;
+use SL::DB::DunningConfig;
+use SL::DB::Employee;
+use SL::DB::Exchangerate;
+use SL::DB::Finanzamt;
+use SL::DB::FollowUp;
+use SL::DB::FollowUpAccess;
+use SL::DB::FollowUpLink;
+use SL::DB::GLTransaction;
+use SL::DB::GenericTranslation;
+use SL::DB::Gifi;
+use SL::DB::History;
+use SL::DB::Inventory;
+use SL::DB::Invoice;
+use SL::DB::InvoiceItem;
+use SL::DB::Language;
+use SL::DB::License;
+use SL::DB::LicenseInvoice;
+use SL::DB::MakeModel;
+use SL::DB::Note;
+use SL::DB::Order;
+use SL::DB::OrderItem;
+use SL::DB::Part;
+use SL::DB::PartsGroup;
+use SL::DB::PartsTax;
+use SL::DB::PaymentTerm;
+use SL::DB::PriceFactor;
+use SL::DB::Pricegroup;
+use SL::DB::Prices;
+use SL::DB::Printer;
+use SL::DB::Project;
+use SL::DB::PurchaseInvoice;
+use SL::DB::RMA;
+use SL::DB::RMAItem;
+use SL::DB::RecordLink;
+use SL::DB::SchemaInfo;
+use SL::DB::SepaExport;
+use SL::DB::SepaExportItem;
+use SL::DB::Shipto;
+use SL::DB::Status;
+use SL::DB::Tax;
+use SL::DB::TaxKey;
+use SL::DB::TaxZone;
+use SL::DB::TodoUserConfig;
+use SL::DB::TransferType;
+use SL::DB::Translation;
+use SL::DB::TranslationPaymentTerm;
+use SL::DB::Unit;
+use SL::DB::UnitsLanguage;
+use SL::DB::Vendor;
+use SL::DB::VendorTax;
+use SL::DB::Warehouse;
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+SL::DB::Helper::ALL: Dependency-only package for all SL::DB::* modules
+
+=head1 SYNOPSIS
+
+ use SL::DB::Helper::ALL;
+
+=head1 DESCRIPTION
+
+This module depends on all modules in SL/DB/*.pm for the convenience
+of being able to write a simple \C<use SL::DB::Helper::ALL> and
+having everything loaded. This is supposed to be used only in the
+Lx-Office console. Normal modules should C<use> only the modules they
+actually need.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
--- /dev/null
+package SL::DB::Helper::Attr;
+
+use strict;
+
+sub auto_make {
+ my ($package, %params) = @_;
+
+ for my $col ($package->meta->columns) {
+ next if $col->primary_key_position; # don't make attr helper for primary keys
+ _make_by_type($package, $col->name, $col->type);
+ }
+
+ return $package;
+}
+
+sub make {
+ my ($package, %params) = @_;
+
+ for my $name (keys %params) {
+ my @types = ref $params{$name} eq 'ARRAY' ? @{ $params{$name} } : ($params{$name});
+ for my $type (@types) {
+ _make_by_type($package, $name, $type);
+ }
+ }
+ return $package;
+}
+
+
+
+sub _make_by_type {
+ my ($package, $name, $type) = @_;
+ _as_number ($package, $name, places => -2) if $type =~ /numeric | real | float/xi;
+ _as_percent($package, $name, places => 0) if $type =~ /numeric | real | float/xi;
+ _as_number ($package, $name, places => 0) if $type =~ /int/xi;
+ _as_date ($package, $name) if $type =~ /date | timestamp/xi;
+}
+
+sub _as_number {
+ my $package = shift;
+ my $attribute = shift;
+ my %params = @_;
+
+ $params{places} = 2 if !defined($params{places});
+
+ no strict 'refs';
+ *{ $package . '::' . $attribute . '_as_number' } = sub {
+ my ($self, $string) = @_;
+
+ $self->$attribute($::form->parse_amount(\%::myconfig, $string)) if @_ > 1;
+
+ return $::form->format_amount(\%::myconfig, $self->$attribute, $params{places});
+ };
+}
+
+sub _as_percent {
+ my $package = shift;
+ my $attribute = shift;
+ my %params = @_;
+
+ $params{places} = 2 if !defined($params{places});
+
+ no strict 'refs';
+ *{ $package . '::' . $attribute . '_as_percent' } = sub {
+ my ($self, $string) = @_;
+
+ $self->$attribute($::form->parse_amount(\%::myconfig, $string) / 100) if @_ > 1;
+
+ return $::form->format_amount(\%::myconfig, 100 * $self->$attribute, $params{places});
+ };
+
+ return 1;
+}
+
+sub _as_date {
+ my $package = shift;
+ my $attribute = shift;
+ my %params = @_;
+
+ no strict 'refs';
+ *{ $package . '::' . $attribute . '_as_date' } = sub {
+ my ($self, $string) = @_;
+
+ if (@_ > 1) {
+ if ($string) {
+ my ($yy, $mm, $dd) = $::locale->parse_date(\%::myconfig, $string);
+ $self->$attribute(DateTime->new(year => $yy, month => $mm, day => $dd));
+ } else {
+ $self->$attribute(undef);
+ }
+ }
+
+ return $self->$attribute
+ ? $::locale->reformat_date(
+ { dateformat => 'yy-mm-dd' },
+ ( $self->$attribute eq 'now'
+ ? DateTime->now
+ : $self->$attribute
+ )->ymd,
+ $::myconfig{dateformat}
+ )
+ : undef;
+ };
+
+ return 1;
+}
+
+1;
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+SL::DB::Helper::Attr - attribute helpers
+
+=head1 SYNOPSIS
+
+ use SL::DB::Helper::Attr;
+ SL::DB::Helper::Attr::make($class,
+ method_name => 'numeric(15,5)',
+ datemethod => 'date'
+ );
+ SL::DB::Helper::Attr::auto_make($class);
+
+=head1 DESCRIPTION
+
+=head1 FUNCTIONS
+
+=head1 BUGS
+
+=head1 AUTHOR
+
+=cut
--- /dev/null
+package SL::DB::Helper::ConventionManager;
+
+use strict;
+
+use Rose::DB::Object::ConventionManager;
+
+use base qw(Rose::DB::Object::ConventionManager);
+
+sub auto_manager_class_name {
+ my $self = shift;
+ my $object_class = shift || $self->meta->class;
+
+ my @parts = split m/::/, $object_class;
+ my $last = pop @parts;
+
+ return join('::', @parts, 'Manager', $last);
+}
+
+# Base name used for 'make_manager_class', e.g. 'get_all',
+# 'update_all'
+sub auto_manager_base_name {
+ return 'all';
+}
+
+sub auto_manager_base_class {
+ return 'SL::DB::Helper::Manager';
+}
+
+1;
--- /dev/null
+package SL::DB::Helper::Manager;
+
+use strict;
+
+use Rose::DB::Object::Manager;
+use base qw(Rose::DB::Object::Manager);
+
+sub make_manager_methods {
+ my $class = shift;
+ my @params = scalar(@_) ? @_ : qw(all);
+ return $class->SUPER::make_manager_methods(@params);
+}
+
+sub find_by {
+ my $class = shift;
+
+ return if !@_;
+ return $class->get_all(query => [ @_ ], limit => 1)->[0];
+}
+
+sub get_first {
+ shift->get_all(
+ limit => 1,
+ )->[0];
+}
+
+1;
--- /dev/null
+package SL::DB::Helpers::Mappings;
+
+use utf8;
+use strict;
+
+# these will not be managed as Rose::DB models, because they are not normalized,
+# significant changes are needed to get them done, or they were done by CRM.
+my @lxoffice_blacklist_permanent = qw(
+ leads
+);
+
+# these are not managed _yet_, but will hopefully at some point.
+# if you are confident that one of these works, remove it here.
+my @lxoffice_blacklist_temp = qw(
+);
+
+my @lxoffice_blacklist = (@lxoffice_blacklist_permanent, @lxoffice_blacklist_temp);
+
+# map table names to their models.
+# unlike rails we have no singular<->plural magic.
+# remeber: tables should be named as the plural of the model name.
+my %lxoffice_package_names = (
+ acc_trans => 'acc_transaction',
+ audittrail => 'audit_trail',
+ ar => 'invoice',
+ ap => 'purchase_invoice',
+ bank_accounts => 'bank_account',
+ buchungsgruppen => 'buchungsgruppe',
+ contacts => 'contact',
+ custom_variable_configs => 'custom_variable_config',
+ custom_variables => 'custom_variable',
+ custom_variables_validity => 'custom_variable_validity',
+ customertax => 'customer_tax',
+ datev => 'datev',
+ defaults => 'default',
+ delivery_orders => 'delivery_order',
+ delivery_order_items => 'delivery_order_item',
+ department => 'department',
+ dpt_trans => 'dpt_trans',
+ drafts => 'draft',
+ dunning => 'dunning',
+ dunning_config => 'dunning_config',
+ employee => 'employee',
+ exchangerate => 'exchangerate',
+ finanzamt => 'finanzamt',
+ follow_up_access => 'follow_up_access',
+ follow_up_links => 'follow_up_link',
+ follow_ups => 'follow_up',
+ generic_translations => 'generic_translation',
+ gifi => 'gifi',
+ gl => 'GLTransaction',
+ history_erp => 'history',
+ inventory => 'inventory',
+ invoice => 'invoice_item',
+ language => 'language',
+ license => 'license',
+ licenseinvoice => 'license_invoice',
+ makemodel => 'make_model',
+ notes => 'note',
+ orderitems => 'order_item',
+ oe => 'order',
+ parts => 'part',
+ partsgroup => 'parts_group',
+ partstax => 'parts_tax',
+ payment_terms => 'payment_term',
+ prices => 'prices',
+ price_factors => 'price_factor',
+ pricegroup => 'pricegroup',
+ printers => 'Printer',
+ record_links => 'record_link',
+ rma => 'RMA',
+ rmaitems => 'RMA_item',
+ sepa_export => 'sepa_export',
+ sepa_export_items => 'sepa_export_item',
+ schema_info => 'schema_info',
+ status => 'status',
+ tax => 'tax',
+ taxkeys => 'tax_key',
+ tax_zones => 'tax_zone',
+ todo_user_config => 'todo_user_config',
+ translation => 'translation',
+ translation_payment_terms => 'translation_payment_term',
+ units => 'unit',
+ units_language => 'units_language',
+ vendortax => 'vendor_tax',
+);
+
+sub get_blacklist {
+ return LXOFFICE => \@lxoffice_blacklist;
+}
+
+sub get_package_names {
+ return LXOFFICE => \%lxoffice_package_names;
+}
+
+sub db {
+ my $string = $_[0];
+ my $lookup = $lxoffice_package_names{$_[0]} ||
+ plurify($lxoffice_package_names{singlify($_[0])});
+
+ for my $thing ($string, $lookup) {
+
+ # best guess? its already the name. like part. camelize it first
+ my $class = "SL::DB::" . camelify($thing);
+ return $class if defined *{ $class. '::' };
+
+ # next, someone wants a manager and pluralized.
+ my $manager = "SL::DB::Manager::" . singlify(camelify($thing));
+ return $manager if defined *{ $manager . '::' };
+ }
+
+ die "Can't resolve '$string' as a database model, sorry. Did you perhaps forgot to load it?";
+}
+
+sub camelify {
+ my ($str) = @_;
+ $str =~ s/_+(.)/uc($1)/ge;
+ ucfirst $str;
+}
+
+sub snakify {
+ my ($str) = @_;
+ $str =~ s/(?<!^)\u(.)/'_' . lc($1)/ge;
+ lcfirst $str;
+}
+
+sub plurify {
+ my ($str) = @_;
+ $str . 's';
+}
+
+sub singlify {
+ my ($str) = @_;
+ local $/ = 's';
+ chomp $str;
+ $str;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+SL::DB::Helpers::Mappings - Rose Table <-> Model mapping information
+
+=head1 SYNOPSIS
+
+ use SL::DB::Helpers::Mappings qw(@blacklist %table2model);
+
+=head1 DESCRIPTION
+
+This modul stores table <-> model mappings used by the
+L<scripts/rose_auto_create_model.pl> script. If you add a new table that has
+custom mappings, add it here.
+
+=head2 db
+
+A special function provided here is E<db>. Without it you'd have to write:
+
+ my $part = SL::DB::Part->new(id => 1234);
+ my @all_parts = SL::DB::Manager::Part->get_all;
+
+with them it becomes:
+
+ my $part = db('part')->new(id => 123);
+ my @all_parts = db('parts')->get_all;
+
+You don't have to care about add that SL::DB:: incantation anymore. Also, a
+simple s at the end will get you the associated Manager class.
+
+db is written to try to make sense of what you give it, but if all fails, it
+will die with an error.
+
+=head1 BUGS
+
+nothing yet
+
+=head1 SEE ALSO
+
+L<scripts/rose_auto_create_model.pl>
+
+=head1 AUTHOR
+
+Sven Schöling <s.schoeling@linet-services.de>
+
+=cut
--- /dev/null
+package SL::DB::Helper::Metadata;
+
+use strict;
+
+use Rose::DB::Object::Metadata;
+use SL::DB::Helper::ConventionManager;
+
+use base qw(Rose::DB::Object::Metadata);
+
+sub convention_manager_class {
+ return 'SL::DB::Helper::ConventionManager';
+}
+
+sub default_manager_base_class {
+ return 'SL::DB::Helper::Manager';
+}
+
+sub initialize {
+ my $self = shift;
+ $self->make_attr_auto_helpers unless $self->is_initialized;
+ $self->SUPER::initialize(@_);
+}
+
+sub make_attr_helpers {
+ my ($self, %params) = @_;
+ SL::DB::Helper::Attr::make($self->class, %params);
+}
+
+sub make_attr_auto_helpers {
+ my ($self) = @_;
+ SL::DB::Helper::Attr::auto_make($self->class);
+}
+
+1;
--- /dev/null
+package SL::DB::Helper::Sorted;
+
+use strict;
+
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT = qw(get_all_sorted make_sort_string);
+
+my %sort_spec;
+
+sub make_sort_string {
+ my ($class, %params) = @_;
+
+ my $sort_spec = _get_sort_spec($class);
+
+ my $sort_dir = defined($params{sort_dir}) ? $params{sort_dir} * 1 : $sort_spec->{default}->[1];
+ my $sort_dir_str = $sort_dir ? 'ASC' : 'DESC';
+
+ my $sort_by = $params{sort_by};
+ $sort_by = $sort_spec->{default}->[0] unless $sort_spec->{columns}->{$sort_by};
+
+ my $nulls_str = '';
+ if ($sort_spec->{nulls}) {
+ $nulls_str = ref($sort_spec->{nulls}) ? ($sort_spec->{nulls}->{$sort_by} || $sort_spec->{nulls}->{default}) : $sort_spec->{nulls};
+ $nulls_str = " NULLS ${nulls_str}" if $nulls_str;
+ }
+
+ my $sort_by_str = $sort_spec->{columns}->{$sort_by};
+ $sort_by_str = [ $sort_by_str ] unless ref($sort_by_str) eq 'ARRAY';
+ $sort_by_str = join(', ', map { "${_} ${sort_dir_str}${nulls_str}" } @{ $sort_by_str });
+
+ return wantarray ? ($sort_by, $sort_dir, $sort_by_str) : $sort_by_str;
+}
+
+sub get_all_sorted {
+ my ($class, %params) = @_;
+ my $sort_str = $class->make_sort_string(sort_by => delete($params{sort_by}), sort_dir => delete($params{sort_dir}));
+
+ return $class->get_all(sort_by => $sort_str, %params);
+}
+
+sub _get_sort_spec {
+ my ($class) = @_;
+ return $sort_spec{$class} ||= _make_sort_spec($class);
+}
+
+sub _make_sort_spec {
+ my ($class) = @_;
+
+ my %sort_spec = $class->_sort_spec if defined &{ "${class}::_sort_spec" };
+
+ my $meta = $class->object_class->meta;
+
+ if (!$sort_spec{default}) {
+ my @primary_keys = $meta->primary_key;
+ $sort_spec{default} = [ "" . $primary_keys[0], 1 ];
+ }
+
+ $sort_spec{columns} ||= { SIMPLE => [ map { "$_" } $meta->columns ] };
+
+ if ($sort_spec{columns}->{SIMPLE}) {
+ my $table = $meta->table;
+
+ if (!ref($sort_spec{columns}->{SIMPLE}) && ($sort_spec{columns}->{SIMPLE} eq 'ALL')) {
+ map { $sort_spec{columns}->{"$_"} ||= "${table}.${_}"} @{ $meta->columns };
+ delete $sort_spec{columns}->{SIMPLE};
+ } else {
+ map { $sort_spec{columns}->{$_} = "${table}.${_}" } @{ delete($sort_spec{columns}->{SIMPLE}) };
+ }
+ }
+
+ return \%sort_spec;
+}
+
+1;
+
+__END__
+
+=encoding utf8
+
+=head1 NAME
+
+SL::DB::Helper::Sorted - Mixin for a manager class that handles
+sorting of database records
+
+=head1 SYNOPSIS
+
+ package SL::DB::Manager::Message;
+
+ use SL::DB::Helper::Sorted;
+
+ sub _sort_spec {
+ return ( columns => { recipient_id => [ 'CASE
+ WHEN recipient_group_id IS NULL THEN lower(recipient.name)
+ ELSE lower(recipient_group.name)
+ END', ],
+ sender_id => [ 'lower(sender.name)', ],
+ created_at => [ 'created_at', ],
+ subject => [ 'lower(subject)', ],
+ status => [ 'NOT COALESCE(unread, FALSE)', 'created_at' ],
+ },
+ default => [ 'status', 1 ],
+ nulls => { default => 'LAST',
+ subject => 'FIRST',
+ }
+ );
+ }
+
+ package SL::Controller::Message;
+
+ sub action_list {
+ my $messages = SL::DB::Manager::Message->get_all_sorted(sort_by => $::form->{sort_by},
+ sort_dir => $::form->{sort_dir});
+ }
+
+=head1 CLASS FUNCTIONS
+
+=over 4
+
+=item C<make_sort_string %params>
+
+Evaluates C<$params{sort_by}> and C<$params{sort_dir}> and returns an
+SQL string suitable for sorting. The package this package is mixed
+into has to provide a method L</_sort_spec> that returns a hash whose
+structure is explained below. That hash is authoritive in which
+columns may be sorted, which column to sort by by default and how to
+handle C<NULL> values.
+
+Returns the SQL string in scalar context. In array context it returns
+three values: the actual column it sorts by (suitable for another call
+to L</make_sort_string>), the actual sort direction (either 0 or 1)
+and the SQL string.
+
+=item C<get_all_sorted %params>
+
+Returns C<< $class->get_all >> with C<sort_by> set to the value
+returned by c<< $class->make_sort_string(%params) >>.
+
+=back
+
+=head1 CLASS FUNCTIONS PROVIDED BY THE MIXING PACKAGE
+
+=over 4
+
+=item C<_sort_spec>
+
+This method is actually not part of this package but can be provided
+by the package this helper is mixed into. If it isn't then all columns
+of the corresponding table (as returned by the model's meta data) will
+be eligible for sorting.
+
+Returns a hash with the following keys:
+
+=over 2
+
+=item C<default>
+
+A two-element array containing the name and direction by which to sort
+in default cases. Example:
+
+ default => [ 'name', 1 ],
+
+Defaults to the table's primary key column (the first column if the
+primary key is composited).
+
+=item C<columns>
+
+A hash reference. Its keys are column names, and its values are SQL
+strings by which to sort. Example:
+
+ columns => { SIMPLE => [ 'transaction_description', 'orddate' ],
+ the_date => 'CASE WHEN oe.quotation THEN oe.quodate ELSE oe.orddate END',
+ customer_name => 'lower(customer.name)',
+ },
+
+If sorting by a column is requested that is not a key in this hash
+then the default column name will be used.
+
+The value can be either a scalar or an array reference. If it's the
+latter then both the sort direction as well as the null handling will
+be appended to each of its members.
+
+The special key C<SIMPLE> can be a scalar or an array reference. If it
+is an array reference then it contains column names that are mapped
+1:1 onto the table's columns. If it is the scalar 'ALL' then all
+columns in that model's meta data are mapped 1:1 unless the C<columns>
+hash already contains a key for that column.
+
+If C<columns> is missing then all columns of the model will be
+eligible for sorting. The list of columns is looked up in the model's
+meta data.
+
+=item C<nulls>
+
+Either a scalar or a hash reference determining where C<NULL> values
+will be sorted. If undefined then the decision is left to the
+database.
+
+If it is a scalar then all the same value will be used for all
+classes. The value is either C<FIRST> or C<LAST>.
+
+If it is a hash reference then its keys are column names (not SQL
+names). The values are either C<FIRST> or C<LAST>. If a column name is
+not found in this hash then the special keu C<default> will be looked
+up and used if it is found.
+
+Example:
+
+ nulls => { transaction_description => 'FIRST',
+ customer_name => 'FIRST',
+ default => 'LAST',
+ },
+
+=back
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
+++ /dev/null
-package SL::DB::Helpers::ALL;
-
-use strict;
-
-use SL::DB::AccTrans;
-use SL::DB::AccTransaction;
-use SL::DB::Assembly;
-use SL::DB::AuditTrail;
-use SL::DB::BankAccount;
-use SL::DB::Bin;
-use SL::DB::Buchungsgruppe;
-use SL::DB::Business;
-use SL::DB::Chart;
-use SL::DB::Contact;
-use SL::DB::CustomVariable;
-use SL::DB::CustomVariableConfig;
-use SL::DB::CustomVariableValidity;
-use SL::DB::Customer;
-use SL::DB::CustomerTax;
-use SL::DB::Datev;
-use SL::DB::Default;
-use SL::DB::DeliveryOrder;
-use SL::DB::DeliveryOrderItem;
-use SL::DB::DeliveryOrderItemsStock;
-use SL::DB::Department;
-use SL::DB::DptTrans;
-use SL::DB::Draft;
-use SL::DB::Dunning;
-use SL::DB::DunningConfig;
-use SL::DB::Employee;
-use SL::DB::Exchangerate;
-use SL::DB::Finanzamt;
-use SL::DB::FollowUp;
-use SL::DB::FollowUpAccess;
-use SL::DB::FollowUpLink;
-use SL::DB::GLTransaction;
-use SL::DB::GenericTranslation;
-use SL::DB::Gifi;
-use SL::DB::History;
-use SL::DB::Inventory;
-use SL::DB::Invoice;
-use SL::DB::InvoiceItem;
-use SL::DB::Language;
-use SL::DB::License;
-use SL::DB::LicenseInvoice;
-use SL::DB::MakeModel;
-use SL::DB::Note;
-use SL::DB::Order;
-use SL::DB::OrderItem;
-use SL::DB::Part;
-use SL::DB::PartsGroup;
-use SL::DB::PartsTax;
-use SL::DB::PaymentTerm;
-use SL::DB::PriceFactor;
-use SL::DB::Pricegroup;
-use SL::DB::Prices;
-use SL::DB::Printer;
-use SL::DB::Project;
-use SL::DB::PurchaseInvoice;
-use SL::DB::RMA;
-use SL::DB::RMAItem;
-use SL::DB::RecordLink;
-use SL::DB::SchemaInfo;
-use SL::DB::SepaExport;
-use SL::DB::SepaExportItem;
-use SL::DB::Shipto;
-use SL::DB::Status;
-use SL::DB::Tax;
-use SL::DB::TaxKey;
-use SL::DB::TaxZone;
-use SL::DB::TodoUserConfig;
-use SL::DB::TransferType;
-use SL::DB::Translation;
-use SL::DB::TranslationPaymentTerm;
-use SL::DB::Unit;
-use SL::DB::UnitsLanguage;
-use SL::DB::Vendor;
-use SL::DB::VendorTax;
-use SL::DB::Warehouse;
-
-1;
-
-__END__
-
-=pod
-
-=head1 NAME
-
-SL::DB::Helpers::ALL: Dependency-only package for all SL::DB::* modules
-
-=head1 SYNOPSIS
-
- use SL::DB::Helpers::ALL;
-
-=head1 DESCRIPTION
-
-This module depends on all modules in SL/DB/*.pm for the convenience
-of being able to write a simple \C<use SL::DB::Helpers::ALL> and
-having everything loaded. This is supposed to be used only in the
-Lx-Office console. Normal modules should C<use> only the modules they
-actually need.
-
-=head1 AUTHOR
-
-Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
-
-=cut
+++ /dev/null
-package SL::DB::Helper::Attr;
-
-use strict;
-
-sub auto_make {
- my ($package, %params) = @_;
-
- for my $col ($package->meta->columns) {
- next if $col->primary_key_position; # don't make attr helper for primary keys
- _make_by_type($package, $col->name, $col->type);
- }
-
- return $package;
-}
-
-sub make {
- my ($package, %params) = @_;
-
- for my $name (keys %params) {
- my @types = ref $params{$name} eq 'ARRAY' ? @{ $params{$name} } : ($params{$name});
- for my $type (@types) {
- _make_by_type($package, $name, $type);
- }
- }
- return $package;
-}
-
-
-
-sub _make_by_type {
- my ($package, $name, $type) = @_;
- _as_number ($package, $name, places => -2) if $type =~ /numeric | real | float/xi;
- _as_percent($package, $name, places => 0) if $type =~ /numeric | real | float/xi;
- _as_number ($package, $name, places => 0) if $type =~ /int/xi;
- _as_date ($package, $name) if $type =~ /date | timestamp/xi;
-}
-
-sub _as_number {
- my $package = shift;
- my $attribute = shift;
- my %params = @_;
-
- $params{places} = 2 if !defined($params{places});
-
- no strict 'refs';
- *{ $package . '::' . $attribute . '_as_number' } = sub {
- my ($self, $string) = @_;
-
- $self->$attribute($::form->parse_amount(\%::myconfig, $string)) if @_ > 1;
-
- return $::form->format_amount(\%::myconfig, $self->$attribute, $params{places});
- };
-}
-
-sub _as_percent {
- my $package = shift;
- my $attribute = shift;
- my %params = @_;
-
- $params{places} = 2 if !defined($params{places});
-
- no strict 'refs';
- *{ $package . '::' . $attribute . '_as_percent' } = sub {
- my ($self, $string) = @_;
-
- $self->$attribute($::form->parse_amount(\%::myconfig, $string) / 100) if @_ > 1;
-
- return $::form->format_amount(\%::myconfig, 100 * $self->$attribute, $params{places});
- };
-
- return 1;
-}
-
-sub _as_date {
- my $package = shift;
- my $attribute = shift;
- my %params = @_;
-
- no strict 'refs';
- *{ $package . '::' . $attribute . '_as_date' } = sub {
- my ($self, $string) = @_;
-
- if (@_ > 1) {
- if ($string) {
- my ($yy, $mm, $dd) = $::locale->parse_date(\%::myconfig, $string);
- $self->$attribute(DateTime->new(year => $yy, month => $mm, day => $dd));
- } else {
- $self->$attribute(undef);
- }
- }
-
- return $self->$attribute
- ? $::locale->reformat_date(
- { dateformat => 'yy-mm-dd' },
- ( $self->$attribute eq 'now'
- ? DateTime->now
- : $self->$attribute
- )->ymd,
- $::myconfig{dateformat}
- )
- : undef;
- };
-
- return 1;
-}
-
-1;
-
-
-1;
-
-__END__
-
-=head1 NAME
-
-SL::DB::Helpers::Attr - attribute helpers
-
-=head1 SYNOPSIS
-
- use SL::DB::Helpers::Attr;
- SL::DB::Helpers::Attr::make($class,
- method_name => 'numeric(15,5)',
- datemethod => 'date'
- );
- SL::DB::Helpers::Attr::auto_make($class);
-
-=head1 DESCRIPTION
-
-=head1 FUNCTIONS
-
-=head1 BUGS
-
-=head1 AUTHOR
-
-=cut
+++ /dev/null
-package SL::DB::Helpers::ConventionManager;
-
-use strict;
-
-use Rose::DB::Object::ConventionManager;
-
-use base qw(Rose::DB::Object::ConventionManager);
-
-sub auto_manager_class_name {
- my $self = shift;
- my $object_class = shift || $self->meta->class;
-
- my @parts = split m/::/, $object_class;
- my $last = pop @parts;
-
- return join('::', @parts, 'Manager', $last);
-}
-
-# Base name used for 'make_manager_class', e.g. 'get_all',
-# 'update_all'
-sub auto_manager_base_name {
- return 'all';
-}
-
-sub auto_manager_base_class {
- return 'SL::DB::Helpers::Manager';
-}
-
-1;
+++ /dev/null
-package SL::DB::Helpers::Manager;
-
-use strict;
-
-use Rose::DB::Object::Manager;
-use base qw(Rose::DB::Object::Manager);
-
-sub make_manager_methods {
- my $class = shift;
- my @params = scalar(@_) ? @_ : qw(all);
- return $class->SUPER::make_manager_methods(@params);
-}
-
-sub find_by {
- my $class = shift;
-
- return if !@_;
- return $class->get_all(query => [ @_ ], limit => 1)->[0];
-}
-
-sub get_first {
- shift->get_all(
- limit => 1,
- )->[0];
-}
-
-1;
+++ /dev/null
-package SL::DB::Helpers::Mappings;
-
-use utf8;
-use strict;
-
-# these will not be managed as Rose::DB models, because they are not normalized,
-# significant changes are needed to get them done, or they were done by CRM.
-my @lxoffice_blacklist_permanent = qw(
- leads
-);
-
-# these are not managed _yet_, but will hopefully at some point.
-# if you are confident that one of these works, remove it here.
-my @lxoffice_blacklist_temp = qw(
-);
-
-my @lxoffice_blacklist = (@lxoffice_blacklist_permanent, @lxoffice_blacklist_temp);
-
-# map table names to their models.
-# unlike rails we have no singular<->plural magic.
-# remeber: tables should be named as the plural of the model name.
-my %lxoffice_package_names = (
- acc_trans => 'acc_transaction',
- audittrail => 'audit_trail',
- ar => 'invoice',
- ap => 'purchase_invoice',
- bank_accounts => 'bank_account',
- buchungsgruppen => 'buchungsgruppe',
- contacts => 'contact',
- custom_variable_configs => 'custom_variable_config',
- custom_variables => 'custom_variable',
- custom_variables_validity => 'custom_variable_validity',
- customertax => 'customer_tax',
- datev => 'datev',
- defaults => 'default',
- delivery_orders => 'delivery_order',
- delivery_order_items => 'delivery_order_item',
- department => 'department',
- dpt_trans => 'dpt_trans',
- drafts => 'draft',
- dunning => 'dunning',
- dunning_config => 'dunning_config',
- employee => 'employee',
- exchangerate => 'exchangerate',
- finanzamt => 'finanzamt',
- follow_up_access => 'follow_up_access',
- follow_up_links => 'follow_up_link',
- follow_ups => 'follow_up',
- generic_translations => 'generic_translation',
- gifi => 'gifi',
- gl => 'GLTransaction',
- history_erp => 'history',
- inventory => 'inventory',
- invoice => 'invoice_item',
- language => 'language',
- license => 'license',
- licenseinvoice => 'license_invoice',
- makemodel => 'make_model',
- notes => 'note',
- orderitems => 'order_item',
- oe => 'order',
- parts => 'part',
- partsgroup => 'parts_group',
- partstax => 'parts_tax',
- payment_terms => 'payment_term',
- prices => 'prices',
- price_factors => 'price_factor',
- pricegroup => 'pricegroup',
- printers => 'Printer',
- record_links => 'record_link',
- rma => 'RMA',
- rmaitems => 'RMA_item',
- sepa_export => 'sepa_export',
- sepa_export_items => 'sepa_export_item',
- schema_info => 'schema_info',
- status => 'status',
- tax => 'tax',
- taxkeys => 'tax_key',
- tax_zones => 'tax_zone',
- todo_user_config => 'todo_user_config',
- translation => 'translation',
- translation_payment_terms => 'translation_payment_term',
- units => 'unit',
- units_language => 'units_language',
- vendortax => 'vendor_tax',
-);
-
-sub get_blacklist {
- return LXOFFICE => \@lxoffice_blacklist;
-}
-
-sub get_package_names {
- return LXOFFICE => \%lxoffice_package_names;
-}
-
-sub db {
- my $string = $_[0];
- my $lookup = $lxoffice_package_names{$_[0]} ||
- plurify($lxoffice_package_names{singlify($_[0])});
-
- for my $thing ($string, $lookup) {
-
- # best guess? its already the name. like part. camelize it first
- my $class = "SL::DB::" . camelify($thing);
- return $class if defined *{ $class. '::' };
-
- # next, someone wants a manager and pluralized.
- my $manager = "SL::DB::Manager::" . singlify(camelify($thing));
- return $manager if defined *{ $manager . '::' };
- }
-
- die "Can't resolve '$string' as a database model, sorry. Did you perhaps forgot to load it?";
-}
-
-sub camelify {
- my ($str) = @_;
- $str =~ s/_+(.)/uc($1)/ge;
- ucfirst $str;
-}
-
-sub snakify {
- my ($str) = @_;
- $str =~ s/(?<!^)\u(.)/'_' . lc($1)/ge;
- lcfirst $str;
-}
-
-sub plurify {
- my ($str) = @_;
- $str . 's';
-}
-
-sub singlify {
- my ($str) = @_;
- local $/ = 's';
- chomp $str;
- $str;
-}
-
-1;
-
-__END__
-
-=head1 NAME
-
-SL::DB::Helpers::Mappings - Rose Table <-> Model mapping information
-
-=head1 SYNOPSIS
-
- use SL::DB::Helpers::Mappings qw(@blacklist %table2model);
-
-=head1 DESCRIPTION
-
-This modul stores table <-> model mappings used by the
-L<scripts/rose_auto_create_model.pl> script. If you add a new table that has
-custom mappings, add it here.
-
-=head2 db
-
-A special function provided here is E<db>. Without it you'd have to write:
-
- my $part = SL::DB::Part->new(id => 1234);
- my @all_parts = SL::DB::Manager::Part->get_all;
-
-with them it becomes:
-
- my $part = db('part')->new(id => 123);
- my @all_parts = db('parts')->get_all;
-
-You don't have to care about add that SL::DB:: incantation anymore. Also, a
-simple s at the end will get you the associated Manager class.
-
-db is written to try to make sense of what you give it, but if all fails, it
-will die with an error.
-
-=head1 BUGS
-
-nothing yet
-
-=head1 SEE ALSO
-
-L<scripts/rose_auto_create_model.pl>
-
-=head1 AUTHOR
-
-Sven Schöling <s.schoeling@linet-services.de>
-
-=cut
+++ /dev/null
-package SL::DB::Helpers::Metadata;
-
-use strict;
-
-use Rose::DB::Object::Metadata;
-use SL::DB::Helpers::ConventionManager;
-
-use base qw(Rose::DB::Object::Metadata);
-
-sub convention_manager_class {
- return 'SL::DB::Helpers::ConventionManager';
-}
-
-sub default_manager_base_class {
- return 'SL::DB::Helpers::Manager';
-}
-
-sub initialize {
- my $self = shift;
- $self->make_attr_auto_helpers unless $self->is_initialized;
- $self->SUPER::initialize(@_);
-}
-
-sub make_attr_helpers {
- my ($self, %params) = @_;
- SL::DB::Helper::Attr::make($self->class, %params);
-}
-
-sub make_attr_auto_helpers {
- my ($self) = @_;
- SL::DB::Helper::Attr::auto_make($self->class);
-}
-
-1;
+++ /dev/null
-package SL::DB::Helpers::Sorted;
-
-use strict;
-
-require Exporter;
-our @ISA = qw(Exporter);
-our @EXPORT = qw(get_all_sorted make_sort_string);
-
-my %sort_spec;
-
-sub make_sort_string {
- my ($class, %params) = @_;
-
- my $sort_spec = _get_sort_spec($class);
-
- my $sort_dir = defined($params{sort_dir}) ? $params{sort_dir} * 1 : $sort_spec->{default}->[1];
- my $sort_dir_str = $sort_dir ? 'ASC' : 'DESC';
-
- my $sort_by = $params{sort_by};
- $sort_by = $sort_spec->{default}->[0] unless $sort_spec->{columns}->{$sort_by};
-
- my $nulls_str = '';
- if ($sort_spec->{nulls}) {
- $nulls_str = ref($sort_spec->{nulls}) ? ($sort_spec->{nulls}->{$sort_by} || $sort_spec->{nulls}->{default}) : $sort_spec->{nulls};
- $nulls_str = " NULLS ${nulls_str}" if $nulls_str;
- }
-
- my $sort_by_str = $sort_spec->{columns}->{$sort_by};
- $sort_by_str = [ $sort_by_str ] unless ref($sort_by_str) eq 'ARRAY';
- $sort_by_str = join(', ', map { "${_} ${sort_dir_str}${nulls_str}" } @{ $sort_by_str });
-
- return wantarray ? ($sort_by, $sort_dir, $sort_by_str) : $sort_by_str;
-}
-
-sub get_all_sorted {
- my ($class, %params) = @_;
- my $sort_str = $class->make_sort_string(sort_by => delete($params{sort_by}), sort_dir => delete($params{sort_dir}));
-
- return $class->get_all(sort_by => $sort_str, %params);
-}
-
-sub _get_sort_spec {
- my ($class) = @_;
- return $sort_spec{$class} ||= _make_sort_spec($class);
-}
-
-sub _make_sort_spec {
- my ($class) = @_;
-
- my %sort_spec = $class->_sort_spec if defined &{ "${class}::_sort_spec" };
-
- my $meta = $class->object_class->meta;
-
- if (!$sort_spec{default}) {
- my @primary_keys = $meta->primary_key;
- $sort_spec{default} = [ "" . $primary_keys[0], 1 ];
- }
-
- $sort_spec{columns} ||= { SIMPLE => [ map { "$_" } $meta->columns ] };
-
- if ($sort_spec{columns}->{SIMPLE}) {
- my $table = $meta->table;
-
- if (!ref($sort_spec{columns}->{SIMPLE}) && ($sort_spec{columns}->{SIMPLE} eq 'ALL')) {
- map { $sort_spec{columns}->{"$_"} ||= "${table}.${_}"} @{ $meta->columns };
- delete $sort_spec{columns}->{SIMPLE};
- } else {
- map { $sort_spec{columns}->{$_} = "${table}.${_}" } @{ delete($sort_spec{columns}->{SIMPLE}) };
- }
- }
-
- return \%sort_spec;
-}
-
-1;
-
-__END__
-
-=encoding utf8
-
-=head1 NAME
-
-SL::DB::Helpers::Sorted - Mixin for a manager class that handles
-sorting of database records
-
-=head1 SYNOPSIS
-
- package SL::DB::Manager::Message;
-
- use SL::DB::Helpers::Sorted;
-
- sub _sort_spec {
- return ( columns => { recipient_id => [ 'CASE
- WHEN recipient_group_id IS NULL THEN lower(recipient.name)
- ELSE lower(recipient_group.name)
- END', ],
- sender_id => [ 'lower(sender.name)', ],
- created_at => [ 'created_at', ],
- subject => [ 'lower(subject)', ],
- status => [ 'NOT COALESCE(unread, FALSE)', 'created_at' ],
- },
- default => [ 'status', 1 ],
- nulls => { default => 'LAST',
- subject => 'FIRST',
- }
- );
- }
-
- package SL::Controller::Message;
-
- sub action_list {
- my $messages = SL::DB::Manager::Message->get_all_sorted(sort_by => $::form->{sort_by},
- sort_dir => $::form->{sort_dir});
- }
-
-=head1 CLASS FUNCTIONS
-
-=over 4
-
-=item C<make_sort_string %params>
-
-Evaluates C<$params{sort_by}> and C<$params{sort_dir}> and returns an
-SQL string suitable for sorting. The package this package is mixed
-into has to provide a method L</_sort_spec> that returns a hash whose
-structure is explained below. That hash is authoritive in which
-columns may be sorted, which column to sort by by default and how to
-handle C<NULL> values.
-
-Returns the SQL string in scalar context. In array context it returns
-three values: the actual column it sorts by (suitable for another call
-to L</make_sort_string>), the actual sort direction (either 0 or 1)
-and the SQL string.
-
-=item C<get_all_sorted %params>
-
-Returns C<< $class->get_all >> with C<sort_by> set to the value
-returned by c<< $class->make_sort_string(%params) >>.
-
-=back
-
-=head1 CLASS FUNCTIONS PROVIDED BY THE MIXING PACKAGE
-
-=over 4
-
-=item C<_sort_spec>
-
-This method is actually not part of this package but can be provided
-by the package this helper is mixed into. If it isn't then all columns
-of the corresponding table (as returned by the model's meta data) will
-be eligible for sorting.
-
-Returns a hash with the following keys:
-
-=over 2
-
-=item C<default>
-
-A two-element array containing the name and direction by which to sort
-in default cases. Example:
-
- default => [ 'name', 1 ],
-
-Defaults to the table's primary key column (the first column if the
-primary key is composited).
-
-=item C<columns>
-
-A hash reference. Its keys are column names, and its values are SQL
-strings by which to sort. Example:
-
- columns => { SIMPLE => [ 'transaction_description', 'orddate' ],
- the_date => 'CASE WHEN oe.quotation THEN oe.quodate ELSE oe.orddate END',
- customer_name => 'lower(customer.name)',
- },
-
-If sorting by a column is requested that is not a key in this hash
-then the default column name will be used.
-
-The value can be either a scalar or an array reference. If it's the
-latter then both the sort direction as well as the null handling will
-be appended to each of its members.
-
-The special key C<SIMPLE> can be a scalar or an array reference. If it
-is an array reference then it contains column names that are mapped
-1:1 onto the table's columns. If it is the scalar 'ALL' then all
-columns in that model's meta data are mapped 1:1 unless the C<columns>
-hash already contains a key for that column.
-
-If C<columns> is missing then all columns of the model will be
-eligible for sorting. The list of columns is looked up in the model's
-meta data.
-
-=item C<nulls>
-
-Either a scalar or a hash reference determining where C<NULL> values
-will be sorted. If undefined then the decision is left to the
-database.
-
-If it is a scalar then all the same value will be used for all
-classes. The value is either C<FIRST> or C<LAST>.
-
-If it is a hash reference then its keys are column names (not SQL
-names). The values are either C<FIRST> or C<LAST>. If a column name is
-not found in this hash then the special keu C<default> will be looked
-up and used if it is found.
-
-Example:
-
- nulls => { transaction_description => 'FIRST',
- customer_name => 'FIRST',
- default => 'LAST',
- },
-
-=back
-
-=back
-
-=head1 BUGS
-
-Nothing here yet.
-
-=head1 AUTHOR
-
-Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
-
-=cut
use strict;
-use SL::DB::Helpers::Manager;
-use base qw(SL::DB::Helpers::Manager);
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
sub object_class { 'SL::DB::DeliveryOrder' }
use strict;
-use SL::DB::Helpers::Manager;
-use base qw(SL::DB::Helpers::Manager);
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
sub object_class { 'SL::DB::Employee' }
use strict;
-use base qw(SL::DB::Helpers::Manager);
+use base qw(SL::DB::Helper::Manager);
sub object_class { 'SL::DB::Invoice' }
use strict;
-use SL::DB::Helpers::Manager;
-use base qw(SL::DB::Helpers::Manager);
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
sub object_class { 'SL::DB::Order' }
use strict;
-use SL::DB::Helpers::Manager;
-use base qw(SL::DB::Helpers::Manager);
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
use Carp;
use SL::DBUtils;
use strict;
-use SL::DB::Helpers::Manager;
-use base qw(SL::DB::Helpers::Manager);
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
sub object_class { 'SL::DB::PurchaseInvoice' }
use strict;
-use base qw(SL::DB::Helpers::Manager);
+use base qw(SL::DB::Helper::Manager);
use Carp;
use List::MoreUtils qw(any);
use SL::DB;
-use SL::DB::Helpers::Attr;
-use SL::DB::Helpers::Metadata;
-use SL::DB::Helpers::Manager;
+use SL::DB::Helper::Attr;
+use SL::DB::Helper::Metadata;
+use SL::DB::Helper::Manager;
use base qw(Rose::DB::Object);
}
sub meta_class {
- return 'SL::DB::Helpers::Metadata';
+ return 'SL::DB::Helper::Metadata';
}
sub _get_manager_class {
if ($::use_rdbo) {
eval {
- require SL::DB::Helpers::Mappings;
+ require SL::DB::Helper::Mappings;
sub db {
- goto &SL::DB::Helpers::Mappings::db;
+ goto &SL::DB::Helper::Mappings::db;
}
} or die $@;
}
use SL::Form;
use SL::Locale;
use SL::LXDebug;
-use SL::DB::Helpers::ALL;
-use SL::DB::Helpers::Mappings;
+use SL::DB::Helper::ALL;
+use SL::DB::Helper::Mappings;
our $form;
our $cgi;
setup();
-my %blacklist = SL::DB::Helpers::Mappings->get_blacklist;
-my %package_names = SL::DB::Helpers::Mappings->get_package_names;
+my %blacklist = SL::DB::Helper::Mappings->get_blacklist;
+my %package_names = SL::DB::Helper::Mappings->get_package_names;
my @tables = ();
if (($ARGV[0] eq '--all') || ($ARGV[0] eq '-a') || ($ARGV[0] eq '--sugar') || ($ARGV[0] eq '-s')) {
use Test::More tests => 12;
-use_ok 'SL::DB::Helpers::ALL';
-use_ok 'SL::DB::Helpers::Mappings', qw(db);
+use_ok 'SL::DB::Helper::ALL';
+use_ok 'SL::DB::Helper::Mappings', qw(db);
is db('part'), 'SL::DB::Part';
is db('parts'), 'SL::DB::Manager::Part';