summaryrefslogtreecommitdiff
path: root/FS/FS/part_event/Action
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2014-11-29 16:36:15 -0800
committerMark Wells <mark@freeside.biz>2014-11-29 16:36:15 -0800
commitea16cbe4a50fa79872a4f08225863f39b7d28c06 (patch)
treeb0048d936d2d89c3e686a64827faaee2292c5aa8 /FS/FS/part_event/Action
parent1af8ff7f48f7259fc99f090c301c84b9680fdb4d (diff)
sales commission events on invoices, #25847
Diffstat (limited to 'FS/FS/part_event/Action')
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm15
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_bill.pm95
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_flat.pm25
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_pkg.pm33
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm20
-rw-r--r--FS/FS/part_event/Action/bill_sales_credit.pm91
-rw-r--r--FS/FS/part_event/Action/bill_sales_credit_pkg_class.pm11
-rw-r--r--FS/FS/part_event/Action/pkg_agent_credit.pm5
-rw-r--r--FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm3
-rw-r--r--FS/FS/part_event/Action/pkg_employee_credit.pm5
-rw-r--r--FS/FS/part_event/Action/pkg_referral_credit.pm24
-rw-r--r--FS/FS/part_event/Action/pkg_sales_credit.pm11
-rw-r--r--FS/FS/part_event/Action/pkg_sales_credit_pkg.pm2
-rw-r--r--FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm4
14 files changed, 276 insertions, 68 deletions
diff --git a/FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm b/FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm
index cb61f1b..488132a 100644
--- a/FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm
+++ b/FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm
@@ -1,21 +1,16 @@
package FS::part_event::Action::Mixin::credit_agent_pkg_class;
-use base qw( FS::part_event::Action::Mixin::credit_pkg );
+
+# calculates a credit percentage on a specific package for use with
+# credit_pkg or credit_bill, based on an agent's commission table
use strict;
use FS::Record qw(qsearchs);
-sub option_fields {
- my $class = shift;
- my %option_fields = $class->SUPER::option_fields;
- delete $option_fields{'percent'};
- %option_fields;
-}
-
sub _calc_credit_percent {
- my( $self, $cust_pkg ) = @_;
+ my( $self, $cust_pkg, $agent ) = @_;
my $agent_pkg_class = qsearchs( 'agent_pkg_class', {
- 'agentnum' => $self->cust_main($cust_pkg)->agentnum,
+ 'agentnum' => $agent->agentnum,
'classnum' => $cust_pkg->part_pkg->classnum,
});
diff --git a/FS/FS/part_event/Action/Mixin/credit_bill.pm b/FS/FS/part_event/Action/Mixin/credit_bill.pm
new file mode 100644
index 0000000..4930e35
--- /dev/null
+++ b/FS/FS/part_event/Action/Mixin/credit_bill.pm
@@ -0,0 +1,95 @@
+package FS::part_event::Action::Mixin::credit_bill;
+
+use strict;
+
+# credit_bill: calculates a credit amount that is some percentage of each
+# line item of an invoice
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ my $class = shift;
+ my @fields = (
+ 'reasonnum' => { 'label' => 'Credit reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'R',
+ },
+ 'percent' => { 'label' => 'Percent',
+ 'type' => 'input-percentage',
+ 'default' => '100',
+ },
+ 'what' => {
+ 'label' => 'Of',
+ 'type' => 'select',
+ #add additional ways to specify in the package def
+ 'options' => [qw( setuprecur setup recur setuprecur_margin setup_margin recur_margin )],
+ 'labels' => {
+ 'setuprecur' => 'Amount charged',
+ 'setup' => 'Setup fee',
+ 'recur' => 'Recurring fee',
+ 'setuprecur_margin' => 'Amount charged minus total cost',
+ 'setup_margin' => 'Setup fee minus setup cost',
+ 'recur_margin' => 'Recurring fee minus recurring cost',
+ },
+ },
+ );
+ if ($class->can('_calc_credit_percent')) {
+ splice @fields, 2, 2; #remove the percentage option
+ }
+ @fields;
+
+}
+
+our %part_pkg_cache;
+
+# arguments:
+# 1. the line item
+# 2. the recipient of the commission; may be FS::sales, FS::agent,
+# FS::access_user, etc. Here we don't use it, but it will be passed through
+# to _calc_credit_percent.
+
+sub _calc_credit {
+ my $self = shift;
+ my $cust_bill_pkg = shift;
+
+ my $what = $self->option('what');
+ my $margin = 1 if $what =~ s/_margin$//;
+
+ my $pkgnum = $cust_bill_pkg->pkgnum;
+ my $cust_pkg = $cust_bill_pkg->cust_pkg;
+
+ my $percent;
+ if ( $self->can('_calc_credit_percent') ) {
+ $percent = $self->_calc_credit_percent($cust_pkg, @_);
+ } else {
+ $percent = $self->option('percent') || 0;
+ }
+
+ my $charge = 0;
+ if ( $what eq 'setup' ) {
+ $charge = $cust_bill_pkg->get('setup');
+ } elsif ( $what eq 'recur' ) {
+ $charge = $cust_bill_pkg->get('recur');
+ } elsif ( $what eq 'setuprecur' ) {
+ $charge = $cust_bill_pkg->get('setup') + $cust_bill_pkg->get('recur');
+ }
+ if ( $margin ) {
+ my $pkgpart = $cust_bill_pkg->pkgpart_override || $cust_pkg->pkgpart;
+ my $part_pkg = $part_pkg_cache{$pkgpart}
+ ||= FS::part_pkg->by_key($pkgpart);
+ if ( $what eq 'setup' ) {
+ $charge -= $part_pkg->get('setup_cost');
+ } elsif ( $what eq 'recur' ) {
+ $charge -= $part_pkg->get('recur_cost');
+ } elsif ( $what eq 'setuprecur' ) {
+ $charge -= $part_pkg->get('setup_cost') + $part_pkg->get('recur_cost');
+ }
+ }
+
+ $charge = 0 if $charge < 0; # e.g. prorate
+ return ($percent * $charge / 100);
+}
+
+1;
diff --git a/FS/FS/part_event/Action/Mixin/credit_flat.pm b/FS/FS/part_event/Action/Mixin/credit_flat.pm
new file mode 100644
index 0000000..374cf5d
--- /dev/null
+++ b/FS/FS/part_event/Action/Mixin/credit_flat.pm
@@ -0,0 +1,25 @@
+package FS::part_event::Action::Mixin::credit_flat;
+
+# credit_flat: return a fixed amount for _calc_credit, specified in the
+# options
+
+use strict;
+
+sub option_fields {
+ (
+ 'reasonnum' => { 'label' => 'Credit reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'R',
+ },
+ 'amount' => { 'label' => 'Credit amount',
+ 'type' => 'money',
+ },
+ );
+}
+
+sub _calc_credit {
+ my $self = shift;
+ $self->option('amount');
+}
+
+1;
diff --git a/FS/FS/part_event/Action/Mixin/credit_pkg.pm b/FS/FS/part_event/Action/Mixin/credit_pkg.pm
index e586f85..400ece9 100644
--- a/FS/FS/part_event/Action/Mixin/credit_pkg.pm
+++ b/FS/FS/part_event/Action/Mixin/credit_pkg.pm
@@ -2,12 +2,19 @@ package FS::part_event::Action::Mixin::credit_pkg;
use strict;
+# credit_pkg: calculates a credit amount that is some percentage of the
+# package charge / cost / margin / some other amount of a package
+#
+# also provides an option field for the percentage, unless the action knows
+# how to calculate its own percentage somehow (has a _calc_credit_percent)
+
sub eventtable_hashref {
{ 'cust_pkg' => 1 };
}
sub option_fields {
- (
+ my $class = shift;
+ my @fields = (
'reasonnum' => { 'label' => 'Credit reason',
'type' => 'select-reason',
'reason_class' => 'R',
@@ -36,12 +43,19 @@ sub option_fields {
},
},
);
+ if ($class->can('_calc_credit_percent')) {
+ splice @fields, 2, 2; #remove the percentage option
+ }
+ @fields;
}
-#my %no_cust_pkg = ( 'setup_cost' => 1 );
+# arguments:
+# 1. cust_pkg
+# 2. recipient of the credit (passed through to _calc_credit_percent)
sub _calc_credit {
- my( $self, $cust_pkg ) = @_;
+ my $self = shift;
+ my $cust_pkg = shift;
my $cust_main = $self->cust_main($cust_pkg);
@@ -59,18 +73,17 @@ sub _calc_credit {
}
}
- my $percent = $self->_calc_credit_percent($cust_pkg);
+ my $percent;
+ if ( $self->can('_calc_credit_percent') ) {
+ $percent = $self->_calc_credit_percent($cust_pkg, @_);
+ } else {
+ $percent = $self->option('percent') || 0;
+ }
- #my @arg = $no_cust_pkg{$what} ? () : ($cust_pkg);
my @arg = ($what eq 'setup_cost') ? () : ($cust_pkg);
sprintf('%.2f', $part_pkg->$what(@arg) * $percent / 100 );
}
-sub _calc_credit_percent {
- my( $self, $cust_pkg ) = @_;
- $self->option('percent');
-}
-
1;
diff --git a/FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm b/FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm
index 5c090ef..61302aa 100644
--- a/FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm
+++ b/FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm
@@ -1,30 +1,16 @@
package FS::part_event::Action::Mixin::credit_sales_pkg_class;
-use base qw( FS::part_event::Action::Mixin::credit_pkg );
use strict;
use FS::Record qw(qsearchs);
use FS::sales_pkg_class;
-sub option_fields {
- my $class = shift;
- my %option_fields = $class->SUPER::option_fields;
-
- delete $option_fields{'percent'};
-
- %option_fields;
-}
-
sub _calc_credit_percent {
- my( $self, $cust_pkg ) = @_;
-
- my $salesnum = $cust_pkg->salesnum;
- $salesnum ||= $self->cust_main($cust_pkg)->salesnum
- if $self->option('cust_main_sales');
+ my( $self, $cust_pkg, $sales ) = @_;
- return 0 unless $salesnum;
+ die "sales record required" unless $sales;
my $sales_pkg_class = qsearchs( 'sales_pkg_class', {
- 'salesnum' => $salesnum,
+ 'salesnum' => $sales->salesnum,
'classnum' => $cust_pkg->part_pkg->classnum,
});
diff --git a/FS/FS/part_event/Action/bill_sales_credit.pm b/FS/FS/part_event/Action/bill_sales_credit.pm
new file mode 100644
index 0000000..3193a81
--- /dev/null
+++ b/FS/FS/part_event/Action/bill_sales_credit.pm
@@ -0,0 +1,91 @@
+package FS::part_event::Action::bill_sales_credit;
+
+# in this order:
+# - pkg_sales_credit invokes NEXT, then appends the 'cust_main_sales' param
+# - credit_bill contains the core _calc_credit logic, and also defines other
+# params
+
+use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
+ FS::part_event::Action::Mixin::credit_bill
+ FS::part_event::Action );
+use FS::Record qw(qsearch qsearchs);
+use FS::Conf;
+use Date::Format qw(time2str);
+
+use strict;
+
+sub description { 'Credit the sales person based on the billed amount'; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+our $date_format;
+
+sub do_action {
+ my( $self, $cust_bill, $cust_event ) = @_;
+
+ $date_format ||= FS::Conf->new->config('date_format') || '%x';
+
+ my $cust_main = $self->cust_main($cust_bill);
+
+ my %salesnum_sales; # salesnum => FS::sales object
+ my %salesnum_amount; # salesnum => credit amount
+ my %pkgnum_pkg; # pkgnum => FS::cust_pkg
+ my %salesnum_pkgnums; # salesnum => [ pkgnum, ... ]
+
+ my @items = qsearch('cust_bill_pkg', { invnum => $cust_bill->invnum,
+ pkgnum => { op => '>', value => '0' }
+ });
+
+ foreach my $cust_bill_pkg (@items) {
+ my $pkgnum = $cust_bill_pkg->pkgnum;
+ my $cust_pkg = $pkgnum_pkg{$pkgnum} ||= $cust_bill_pkg->cust_pkg;
+
+ my $salesnum = $cust_pkg->salesnum;
+ $salesnum ||= $cust_main->salesnum
+ if $self->option('cust_main_sales');
+ my $sales = $salesnum_sales{$salesnum}
+ ||= FS::sales->by_key($salesnum);
+
+ next if !$sales; #no sales person, no credit
+
+ my $amount = $self->_calc_credit($cust_bill_pkg, $sales);
+
+ if ($amount > 0) {
+ $salesnum_amount{$salesnum} ||= 0;
+ $salesnum_amount{$salesnum} += $amount;
+ push @{ $salesnum_pkgnums{$salesnum} ||= [] }, $pkgnum;
+ }
+ }
+
+ foreach my $salesnum (keys %salesnum_amount) {
+ my $amount = sprintf('%.2f', $salesnum_amount{$salesnum});
+ next if $amount < 0.005;
+
+ my $sales = $salesnum_sales{$salesnum};
+
+ my $sales_cust_main = $sales->sales_cust_main;
+ die "No customer record for sales person ". $sales->salesperson
+ unless $sales->sales_custnum;
+
+ my $reasonnum = $self->option('reasonnum');
+
+ my $desc = 'from invoice #'. $cust_bill->display_invnum .
+ ' ('. time2str($date_format, $cust_bill->_date) . ')';
+ # could also show custnum and pkgnums here?
+ my $error = $sales_cust_main->credit(
+ $amount,
+ \$reasonnum,
+ 'eventnum' => $cust_event->eventnum,
+ 'addlinfo' => $desc,
+ 'commission_salesnum' => $sales->salesnum,
+ );
+ die "Error crediting customer ". $sales_cust_main->custnum.
+ " for sales commission: $error"
+ if $error;
+ } # foreach $salesnum
+
+}
+
+1;
diff --git a/FS/FS/part_event/Action/bill_sales_credit_pkg_class.pm b/FS/FS/part_event/Action/bill_sales_credit_pkg_class.pm
new file mode 100644
index 0000000..91442b9
--- /dev/null
+++ b/FS/FS/part_event/Action/bill_sales_credit_pkg_class.pm
@@ -0,0 +1,11 @@
+package FS::part_event::Action::bill_sales_credit_pkg_class;
+
+use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
+ FS::part_event::Action::Mixin::credit_bill
+ FS::part_event::Action::Mixin::credit_sales_pkg_class
+ FS::part_event::Action::bill_sales_credit
+ );
+
+sub description { "Credit the sales person based on their commission percentage for the package's class"; }
+
+1;
diff --git a/FS/FS/part_event/Action/pkg_agent_credit.pm b/FS/FS/part_event/Action/pkg_agent_credit.pm
index 494c40e..65f8c27 100644
--- a/FS/FS/part_event/Action/pkg_agent_credit.pm
+++ b/FS/FS/part_event/Action/pkg_agent_credit.pm
@@ -1,7 +1,8 @@
package FS::part_event::Action::pkg_agent_credit;
use strict;
-use base qw( FS::part_event::Action::pkg_referral_credit );
+use base qw( FS::part_event::Action::Mixin::credit_flat
+ FS::part_event::Action );
sub description { 'Credit the agent a specific amount'; }
@@ -18,7 +19,7 @@ sub do_action {
my $agent_cust_main = $agent->agent_cust_main;
#? or return "No customer record for agent ". $agent->agent;
- my $amount = $self->_calc_credit($cust_pkg);
+ my $amount = $self->_calc_credit($cust_pkg, $agent);
return '' unless $amount > 0;
my $reasonnum = $self->option('reasonnum');
diff --git a/FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm b/FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm
index 3dcf668..92c1556 100644
--- a/FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm
+++ b/FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm
@@ -1,7 +1,8 @@
package FS::part_event::Action::pkg_agent_credit_pkg_class;
use strict;
-use base qw( FS::part_event::Action::Mixin::credit_agent_pkg_class
+use base qw( FS::part_event::Action::Mixin::credit_pkg
+ FS::part_event::Action::Mixin::credit_agent_pkg_class
FS::part_event::Action::pkg_agent_credit );
sub description { 'Credit the agent an amount based on their commission percentage for the referred package class'; }
diff --git a/FS/FS/part_event/Action/pkg_employee_credit.pm b/FS/FS/part_event/Action/pkg_employee_credit.pm
index 64dd8b2..6cbe9bc 100644
--- a/FS/FS/part_event/Action/pkg_employee_credit.pm
+++ b/FS/FS/part_event/Action/pkg_employee_credit.pm
@@ -1,7 +1,8 @@
package FS::part_event::Action::pkg_employee_credit;
use strict;
-use base qw( FS::part_event::Action::pkg_referral_credit );
+use base qw( FS::part_event::Action::Mixin::credit_flat
+ FS::part_event::Action );
sub description { 'Credit the ordering employee a specific amount'; }
@@ -18,7 +19,7 @@ sub do_action {
my $employee_cust_main = $employee->user_cust_main;
#? or return "No customer record for employee ". $employee->username;
- my $amount = $self->_calc_credit($cust_pkg);
+ my $amount = $self->_calc_credit($cust_pkg, $employee);
return '' unless $amount > 0;
my $reasonnum = $self->option('reasonnum');
diff --git a/FS/FS/part_event/Action/pkg_referral_credit.pm b/FS/FS/part_event/Action/pkg_referral_credit.pm
index e7c92d6..9d7bbf8 100644
--- a/FS/FS/part_event/Action/pkg_referral_credit.pm
+++ b/FS/FS/part_event/Action/pkg_referral_credit.pm
@@ -1,7 +1,8 @@
package FS::part_event::Action::pkg_referral_credit;
use strict;
-use base qw( FS::part_event::Action );
+use base qw( FS::part_event::Action::Mixin::credit_flat
+ FS::part_event::Action );
sub description { 'Credit the referring customer a specific amount'; }
@@ -9,19 +10,6 @@ sub eventtable_hashref {
{ 'cust_pkg' => 1 };
}
-sub option_fields {
- (
- 'reasonnum' => { 'label' => 'Credit reason',
- 'type' => 'select-reason',
- 'reason_class' => 'R',
- },
- 'amount' => { 'label' => 'Credit amount',
- 'type' => 'money',
- },
- );
-
-}
-
sub do_action {
my( $self, $cust_pkg, $cust_event ) = @_;
@@ -35,7 +23,7 @@ sub do_action {
return 'Referring customer is cancelled'
if $referring_cust_main->status eq 'cancelled';
- my $amount = $self->_calc_credit($cust_pkg);
+ my $amount = $self->_calc_credit($cust_pkg, $referring_cust_main);
return '' unless $amount > 0;
my $reasonnum = $self->option('reasonnum');
@@ -53,10 +41,4 @@ sub do_action {
}
-sub _calc_credit {
- my( $self, $cust_pkg ) = @_;
-
- $self->option('amount');
-}
-
1;
diff --git a/FS/FS/part_event/Action/pkg_sales_credit.pm b/FS/FS/part_event/Action/pkg_sales_credit.pm
index e7551cd..3c569ca 100644
--- a/FS/FS/part_event/Action/pkg_sales_credit.pm
+++ b/FS/FS/part_event/Action/pkg_sales_credit.pm
@@ -1,12 +1,15 @@
package FS::part_event::Action::pkg_sales_credit;
-use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
- FS::part_event::Action::pkg_referral_credit );
+use base qw( FS::part_event::Action::Mixin::credit_flat
+ FS::part_event::Action );
use strict;
sub description { 'Credit the sales person a specific amount'; }
-#a little false laziness w/pkg_referral_credit
+sub eventtable_hashref {
+ { 'cust_pkg' => 1 };
+}
+
sub do_action {
my( $self, $cust_pkg, $cust_event ) = @_;
@@ -24,7 +27,7 @@ sub do_action {
my $sales_cust_main = $sales->sales_cust_main;
#? or return "No customer record for sales person ". $sales->salesperson;
- my $amount = $self->_calc_credit($cust_pkg);
+ my $amount = $self->_calc_credit($cust_pkg, $sales);
return '' unless $amount > 0;
my $reasonnum = $self->option('reasonnum');
diff --git a/FS/FS/part_event/Action/pkg_sales_credit_pkg.pm b/FS/FS/part_event/Action/pkg_sales_credit_pkg.pm
index 9b13cd8..bd165f1 100644
--- a/FS/FS/part_event/Action/pkg_sales_credit_pkg.pm
+++ b/FS/FS/part_event/Action/pkg_sales_credit_pkg.pm
@@ -1,4 +1,6 @@
package FS::part_event::Action::pkg_sales_credit_pkg;
+
+# yes, they must be in this order
use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
FS::part_event::Action::Mixin::credit_pkg
FS::part_event::Action::pkg_sales_credit );
diff --git a/FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm b/FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm
index c69c004..53ffc6c 100644
--- a/FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm
+++ b/FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm
@@ -1,8 +1,10 @@
package FS::part_event::Action::pkg_sales_credit_pkg_class;
use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
+ FS::part_event::Action::Mixin::credit_pkg
FS::part_event::Action::Mixin::credit_sales_pkg_class
- FS::part_event::Action::pkg_sales_credit );
+ FS::part_event::Action::pkg_sales_credit
+ );
sub description { "Credit the package sales person an amount based on their commission percentage for the package's class"; }