1 package FS::commission_schedule;
2 use base qw( FS::o2m_Common FS::Record );
5 use FS::Record qw( qsearch qsearchs );
6 use FS::commission_rate;
9 tie our %basis_options, 'Tie::IxHash', (
10 setuprecur => 'Total sales',
11 setup => 'One-time and setup charges',
12 recur => 'Recurring charges',
13 setup_cost => 'Setup costs',
14 recur_cost => 'Recurring costs',
15 setup_margin => 'Setup charges minus costs',
16 recur_margin_permonth => 'Monthly recurring charges minus costs',
21 FS::commission_schedule - Object methods for commission_schedule records
25 use FS::commission_schedule;
27 $record = new FS::commission_schedule \%hash;
28 $record = new FS::commission_schedule { 'column' => 'value' };
30 $error = $record->insert;
32 $error = $new_record->replace($old_record);
34 $error = $record->delete;
36 $error = $record->check;
40 An FS::commission_schedule object represents a bundle of one or more
41 commission rates for invoices. FS::commission_schedule inherits from
42 FS::Record. The following fields are currently supported:
46 =item schedulenum - primary key
48 =item schedulename - descriptive name
50 =item reasonnum - the credit reason (L<FS::reason>) that will be assigned
51 to these commission credits
53 =item basis - for percentage credits, which component of the invoice charges
54 the percentage will be calculated on:
55 - setuprecur (total charges)
60 - setup_margin (setup - setup_cost)
61 - recur_margin_permonth ((recur - recur_cost) / freq)
71 Creates a new commission schedule. To add the object to the database, see
76 sub table { 'commission_schedule'; }
80 Adds this record to the database. If there is an error, returns the error,
81 otherwise returns false.
85 Delete this record from the database.
91 # don't allow the schedule to be removed if it's still linked to events
92 if ($self->part_event) {
93 return 'This schedule is still in use.'; # UI should be smarter
96 'table' => 'commission_rate',
101 =item replace OLD_RECORD
103 Replaces the OLD_RECORD with this one in the database. If there is an error,
104 returns the error, otherwise returns false.
108 Checks all fields to make sure this is a valid record. If there is
109 an error, returns the error, otherwise returns false. Called by the insert
118 $self->ut_numbern('schedulenum')
119 || $self->ut_text('schedulename')
120 || $self->ut_number('reasonnum')
121 || $self->ut_enum('basis', [ keys %basis_options ])
123 return $error if $error;
130 Returns a list of billing events (L<FS::part_event> objects) that pay
131 commission on this schedule.
137 map { $_->part_event }
138 qsearch('part_event_option', {
139 optionname => 'schedulenum',
140 optionvalue => $self->schedulenum,
145 =item calc_credit INVOICE
147 Takes an L<FS::cust_bill> object and calculates credit on this schedule.
148 Returns the amount to credit. If there's no rate defined for this invoice,
153 # Some false laziness w/ FS::part_event::Action::Mixin::credit_bill.
154 # this is a little different in that we calculate the credit on the whole
159 my $cust_bill = shift;
160 die "cust_bill record required" if !$cust_bill or !$cust_bill->custnum;
161 # count invoices before or including this one
162 my $cycle = FS::cust_bill->count('custnum = ? AND _date <= ?',
166 my $rate = qsearchs('commission_rate', {
167 schedulenum => $self->schedulenum,
170 # we might do something with a rate that applies "after the end of the
171 # schedule" (cycle = 0 or something) so that this can do commissions with
172 # no end date. add that here if there's a need.
176 if ( $rate->percent ) {
177 my $what = $self->basis;
178 my $cost = ($what =~ /_cost/ ? 1 : 0);
179 my $margin = ($what =~ /_margin/ ? 1 : 0);
181 foreach my $cust_bill_pkg ( $cust_bill->cust_bill_pkg ) {
184 next if !$cust_bill_pkg->pkgnum; # exclude taxes and fees
186 my $cust_pkg = $cust_bill_pkg->cust_pkg;
187 if ( $margin or $cost ) {
188 # look up package costs only if we need them
189 my $pkgpart = $cust_bill_pkg->pkgpart_override || $cust_pkg->pkgpart;
190 my $part_pkg = $part_pkg_cache{$pkgpart}
191 ||= FS::part_pkg->by_key($pkgpart);
194 $charge = $part_pkg->get($what);
196 $charge = $part_pkg->$what($cust_pkg);
199 $charge = ($charge || 0) * ($cust_pkg->quantity || 1);
203 if ( $what eq 'setup' ) {
204 $charge = $cust_bill_pkg->get('setup');
205 } elsif ( $what eq 'recur' ) {
206 $charge = $cust_bill_pkg->get('recur');
207 } elsif ( $what eq 'setuprecur' ) {
208 $charge = $cust_bill_pkg->get('setup') +
209 $cust_bill_pkg->get('recur');
213 $amount += ($charge * $rate->percent / 100);
216 } # if $rate->percent
218 if ( $rate->amount ) {
219 $amount += $rate->amount;
222 $amount = sprintf('%.2f', $amount + 0.005);
230 L<FS::Record>, L<FS::part_event>, L<FS::commission_rate>