fix sync_bill_date when customer has no other packages, minor fallout from #39822
[freeside.git] / FS / FS / Query.pm
1 package FS::Query;
2
3 use strict;
4 use FS::Record; # don't import qsearch
5 use Storable 'dclone';
6
7 =head1 NAME
8
9 FS::Query - A thin wrapper around qsearch argument hashes.
10
11 =head1 DESCRIPTION
12
13 This module exists because we pass qsearch argument lists around a lot,
14 and add new joins or WHERE expressions in several stages, and I got tired
15 of doing this:
16
17   my $andwhere = "mycolumn IN('perl','python','javascript')";
18   if ( ($search->{hashref} and keys( %{$search->{hashref}} ))
19        or $search->{extra_sql} =~ /^\s*WHERE/ ) {
20     $search->{extra_sql} .= " AND $andwhere";
21   } else {
22     $search->{extra_sql} = " WHERE $andwhere ";
23   }
24
25 and then having it fail under some conditions if it's done wrong (as the above
26 example is, obviously).
27
28 We may eventually switch over to SQL::Abstract or something for this, but for
29 now it's a couple of crude manipulations and a wrapper to qsearch.
30
31 =head1 METHODS
32
33 =over 4
34
35 =item new HASHREF
36
37 Turns HASHREF (a qsearch argument list) into an FS::Query object. None of
38 the params are really required, but you should at least supply C<table>.
39
40 In the Future this may do a lot more stuff.
41
42 =cut
43
44 sub new {
45   my ($class, $hashref) = @_;
46
47   my $self = bless {
48     table     => '',
49     select    => '*',
50     hashref   => {},
51     addl_from => '',
52     extra_sql => '',
53     order_by  => '',
54     %$hashref,
55   };
56   # load FS::$table? validate anything?
57   $self;
58 }
59
60 =item clone
61
62 Returns another object that's a copy of this one.
63
64 =cut
65
66 sub clone {
67   my $self = shift;
68   $self->new( dclone($self) );
69 }
70
71 =item and_where EXPR
72
73 Adds a constraint to the WHERE clause of the query. All other constraints in
74 the WHERE clause should be joined with AND already; if not, they should be
75 grouped with parentheses.
76
77 =cut
78
79 sub and_where {
80   my $self = shift;
81   my $where = shift;
82
83   if ($self->{extra_sql} =~ /^\s*(?:WHERE|AND)\s+(.*)/is) {
84     $where = "($where) AND $1";
85   }
86   if (keys %{ $self->{hashref} }) {
87     $where = " AND $where";
88   } else {
89     $where = " WHERE $where";
90   }
91   $self->{extra_sql} = $where;
92
93   return $self;
94 }
95
96 =item qsearch
97
98 Runs the query and returns all results.
99
100 =cut
101
102 sub qsearch {
103   my $self = shift;
104   FS::Record::qsearch({ %$self });
105 }
106
107 =item qsearchs
108
109 Runs the query and returns only one result.
110
111 =cut
112
113 sub qsearchs {
114   my $self = shift;
115   FS::Record::qsearchs({ %$self });
116 }
117
118 1;