summaryrefslogtreecommitdiff
path: root/FS/FS
diff options
context:
space:
mode:
Diffstat (limited to 'FS/FS')
-rw-r--r--FS/FS/AccessRight.pm295
-rw-r--r--FS/FS/CGI.pm425
-rw-r--r--FS/FS/ClientAPI.pm37
-rw-r--r--FS/FS/ClientAPI/Agent.pm125
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm1291
-rw-r--r--FS/FS/ClientAPI/Signup.pm514
-rw-r--r--FS/FS/ClientAPI/passwd.pm46
-rw-r--r--FS/FS/ClientAPI_SessionCache.pm78
-rw-r--r--FS/FS/Conf.pm2207
-rw-r--r--FS/FS/ConfDefaults.pm73
-rw-r--r--FS/FS/ConfItem.pm63
-rw-r--r--FS/FS/Conf_compat17.pm2196
-rw-r--r--FS/FS/Cron/backup.pm43
-rw-r--r--FS/FS/Cron/bill.pm150
-rw-r--r--FS/FS/Cron/expire_user_pref.pm20
-rw-r--r--FS/FS/Cron/notify.pm149
-rw-r--r--FS/FS/Cron/vacuum.pm23
-rw-r--r--FS/FS/CurrentUser.pm67
-rw-r--r--FS/FS/Daemon.pm92
-rw-r--r--FS/FS/InitHandler.pm91
-rw-r--r--FS/FS/Misc.pm576
-rw-r--r--FS/FS/Misc/prune.pm126
-rw-r--r--FS/FS/Msgcat.pm98
-rw-r--r--FS/FS/Pony.pm23
-rw-r--r--FS/FS/Record.pm2351
-rw-r--r--FS/FS/Report.pm46
-rw-r--r--FS/FS/Report/Table.pm27
-rw-r--r--FS/FS/Report/Table/Monthly.pm378
-rw-r--r--FS/FS/Schema.pm1962
-rw-r--r--FS/FS/SearchCache.pm96
-rw-r--r--FS/FS/Setup.pm525
-rw-r--r--FS/FS/TicketSystem.pm30
-rw-r--r--FS/FS/TicketSystem/RT_External.pm353
-rw-r--r--FS/FS/TicketSystem/RT_Internal.pm29
-rw-r--r--FS/FS/TicketSystem/RT_Libs.pm10
-rw-r--r--FS/FS/UI/Web.pm573
-rw-r--r--FS/FS/UI/bytecount.pm96
-rw-r--r--FS/FS/UID.pm390
-rw-r--r--FS/FS/Upgrade.pm117
-rw-r--r--FS/FS/XMLRPC.pm166
-rw-r--r--FS/FS/access_group.pm162
-rw-r--r--FS/FS/access_groupagent.pm134
-rw-r--r--FS/FS/access_right.pm127
-rw-r--r--FS/FS/access_user.pm433
-rw-r--r--FS/FS/access_user_pref.pm129
-rw-r--r--FS/FS/access_usergroup.pm145
-rw-r--r--FS/FS/acct_rt_transaction.pm316
-rw-r--r--FS/FS/acct_snarf.pm128
-rwxr-xr-xFS/FS/addr_block.pm341
-rw-r--r--FS/FS/agent.pm445
-rw-r--r--FS/FS/agent_payment_gateway.pm139
-rw-r--r--FS/FS/agent_type.pm191
-rw-r--r--FS/FS/banned_pay.pm136
-rw-r--r--FS/FS/cdr.pm672
-rw-r--r--FS/FS/cdr_calltype.pm115
-rw-r--r--FS/FS/cdr_carrier.pm116
-rw-r--r--FS/FS/cdr_type.pm119
-rw-r--r--FS/FS/cdr_upstream_rate.pm138
-rw-r--r--FS/FS/clientapi_session.pm121
-rw-r--r--FS/FS/clientapi_session_field.pm126
-rw-r--r--FS/FS/conf.pm114
-rw-r--r--FS/FS/cust_bill.pm2888
-rw-r--r--FS/FS/cust_bill_ApplicationCommon.pm390
-rw-r--r--FS/FS/cust_bill_event.pm380
-rw-r--r--FS/FS/cust_bill_pay.pm164
-rw-r--r--FS/FS/cust_bill_pay_batch.pm120
-rw-r--r--FS/FS/cust_bill_pay_pkg.pm141
-rw-r--r--FS/FS/cust_bill_pkg.pm320
-rw-r--r--FS/FS/cust_bill_pkg_detail.pm124
-rw-r--r--FS/FS/cust_credit.pm595
-rw-r--r--FS/FS/cust_credit_bill.pm168
-rw-r--r--FS/FS/cust_credit_bill_pkg.pm141
-rw-r--r--FS/FS/cust_credit_refund.pm186
-rw-r--r--FS/FS/cust_event.pm408
-rw-r--r--FS/FS/cust_main.pm6250
-rw-r--r--FS/FS/cust_main_Mixin.pm269
-rw-r--r--FS/FS/cust_main_county.pm291
-rw-r--r--FS/FS/cust_main_invoice.pm173
-rw-r--r--FS/FS/cust_main_note.pm131
-rw-r--r--FS/FS/cust_pay.pm888
-rw-r--r--FS/FS/cust_pay_batch.pm277
-rw-r--r--FS/FS/cust_pay_pending.pm229
-rw-r--r--FS/FS/cust_pay_refund.pm188
-rw-r--r--FS/FS/cust_pay_void.pm225
-rw-r--r--FS/FS/cust_pkg.pm2091
-rw-r--r--FS/FS/cust_pkg_option.pm115
-rw-r--r--FS/FS/cust_pkg_reason.pm122
-rw-r--r--FS/FS/cust_refund.pm353
-rw-r--r--FS/FS/cust_svc.pm709
-rw-r--r--FS/FS/cust_tax_exempt.pm151
-rw-r--r--FS/FS/cust_tax_exempt_pkg.pm136
-rw-r--r--FS/FS/domain_record.pm438
-rw-r--r--FS/FS/export_svc.pm322
-rw-r--r--FS/FS/h_Common.pm124
-rw-r--r--FS/FS/h_cust_bill.pm33
-rw-r--r--FS/FS/h_cust_credit.pm33
-rw-r--r--FS/FS/h_cust_pay.pm33
-rw-r--r--FS/FS/h_cust_svc.pm161
-rw-r--r--FS/FS/h_cust_tax_exempt.pm40
-rw-r--r--FS/FS/h_domain_record.pm33
-rw-r--r--FS/FS/h_svc_acct.pm78
-rw-r--r--FS/FS/h_svc_broadband.pm33
-rw-r--r--FS/FS/h_svc_domain.pm33
-rw-r--r--FS/FS/h_svc_external.pm33
-rw-r--r--FS/FS/h_svc_forward.pm85
-rw-r--r--FS/FS/h_svc_phone.pm33
-rw-r--r--FS/FS/h_svc_www.pm67
-rw-r--r--FS/FS/inventory_class.pm164
-rw-r--r--FS/FS/inventory_item.pm204
-rw-r--r--FS/FS/m2m_Common.pm144
-rw-r--r--FS/FS/m2name_Common.pm177
-rw-r--r--FS/FS/msgcat.pm133
-rw-r--r--FS/FS/nas.pm150
-rw-r--r--FS/FS/option_Common.pm345
-rw-r--r--FS/FS/part_bill_event.pm363
-rw-r--r--FS/FS/part_event.pm428
-rw-r--r--FS/FS/part_event/Action.pm224
-rw-r--r--FS/FS/part_event/Action/addpost.pm24
-rw-r--r--FS/FS/part_event/Action/apply.pm28
-rw-r--r--FS/FS/part_event/Action/bill.pm30
-rw-r--r--FS/FS/part_event/Action/cancel.pm35
-rw-r--r--FS/FS/part_event/Action/collect.pm30
-rw-r--r--FS/FS/part_event/Action/cust_bill_batch.pm31
-rw-r--r--FS/FS/part_event/Action/cust_bill_comp.pm34
-rw-r--r--FS/FS/part_event/Action/cust_bill_fee_percent.pm40
-rw-r--r--FS/FS/part_event/Action/cust_bill_realtime_card.pm32
-rw-r--r--FS/FS/part_event/Action/cust_bill_realtime_check.pm32
-rw-r--r--FS/FS/part_event/Action/cust_bill_realtime_lec.pm32
-rw-r--r--FS/FS/part_event/Action/cust_bill_send.pm27
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_agent.pm44
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_alternate.pm35
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm56
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_if_newest.pm40
-rw-r--r--FS/FS/part_event/Action/cust_bill_spool_csv.pm64
-rw-r--r--FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm48
-rw-r--r--FS/FS/part_event/Action/fee.pm33
-rw-r--r--FS/FS/part_event/Action/suspend.pm36
-rw-r--r--FS/FS/part_event/Action/suspend_if_pkgpart.pm42
-rw-r--r--FS/FS/part_event/Action/suspend_unless_pkgpart.pm42
-rw-r--r--FS/FS/part_event/Condition.pm412
-rw-r--r--FS/FS/part_event/Condition/agent.pm37
-rw-r--r--FS/FS/part_event/Condition/agent_type.pm40
-rw-r--r--FS/FS/part_event/Condition/balance.pm48
-rw-r--r--FS/FS/part_event/Condition/balance_age.pm77
-rw-r--r--FS/FS/part_event/Condition/balance_under.pm42
-rw-r--r--FS/FS/part_event/Condition/cust_bill_age.pm75
-rw-r--r--FS/FS/part_event/Condition/cust_bill_has_service.pm54
-rw-r--r--FS/FS/part_event/Condition/cust_bill_owed.pm54
-rw-r--r--FS/FS/part_event/Condition/cust_bill_owed_under.pm49
-rw-r--r--FS/FS/part_event/Condition/cust_pay_batch_declined.pm51
-rw-r--r--FS/FS/part_event/Condition/cust_status.pm32
-rw-r--r--FS/FS/part_event/Condition/every.pm67
-rw-r--r--FS/FS/part_event/Condition/once.pm55
-rw-r--r--FS/FS/part_event/Condition/payby.pm50
-rw-r--r--FS/FS/part_event/Condition/pkg_class.pm38
-rw-r--r--FS/FS/part_event/Condition/pkg_status.pm37
-rw-r--r--FS/FS/part_event_condition.pm352
-rw-r--r--FS/FS/part_event_condition_option.pm151
-rw-r--r--FS/FS/part_event_condition_option_option.pm129
-rw-r--r--FS/FS/part_event_option.pm213
-rw-r--r--FS/FS/part_export.pm469
-rw-r--r--FS/FS/part_export/acct_plesk.pm121
-rw-r--r--FS/FS/part_export/acct_sql.pm310
-rw-r--r--FS/FS/part_export/apache.pm47
-rw-r--r--FS/FS/part_export/artera_turbo.pm181
-rw-r--r--FS/FS/part_export/bind.pm35
-rw-r--r--FS/FS/part_export/bind_slave.pm28
-rw-r--r--FS/FS/part_export/bsdshell.pm25
-rw-r--r--FS/FS/part_export/communigate_pro.pm178
-rw-r--r--FS/FS/part_export/communigate_pro_singledomain.pm37
-rw-r--r--FS/FS/part_export/cp.pm161
-rw-r--r--FS/FS/part_export/cpanel.pm192
-rw-r--r--FS/FS/part_export/cyrus.pm120
-rw-r--r--FS/FS/part_export/domain_shellcommands.pm165
-rw-r--r--FS/FS/part_export/domain_sql.pm238
-rw-r--r--FS/FS/part_export/everyone_net.pm132
-rw-r--r--FS/FS/part_export/forward_shellcommands.pm182
-rw-r--r--FS/FS/part_export/http.pm134
-rw-r--r--FS/FS/part_export/infostreet.pm277
-rw-r--r--FS/FS/part_export/ldap.pm294
-rw-r--r--FS/FS/part_export/nas_wrapper.pm311
-rw-r--r--FS/FS/part_export/null.pm13
-rw-r--r--FS/FS/part_export/passwdfile.pm18
-rw-r--r--FS/FS/part_export/postfix.pm32
-rw-r--r--FS/FS/part_export/prizm.pm540
-rw-r--r--FS/FS/part_export/radiator.pm167
-rw-r--r--FS/FS/part_export/router.pm375
-rw-r--r--FS/FS/part_export/shellcommands.pm399
-rw-r--r--FS/FS/part_export/shellcommands_withdomain.pm112
-rw-r--r--FS/FS/part_export/snmp.pm256
-rw-r--r--FS/FS/part_export/sqlmail.pm220
-rw-r--r--FS/FS/part_export/sqlradius.pm722
-rw-r--r--FS/FS/part_export/sqlradius_withdomain.pm28
-rw-r--r--FS/FS/part_export/sysvshell.pm25
-rw-r--r--FS/FS/part_export/textradius.pm191
-rw-r--r--FS/FS/part_export/trango.pm434
-rw-r--r--FS/FS/part_export/vpopmail.pm254
-rw-r--r--FS/FS/part_export/www_plesk.pm138
-rw-r--r--FS/FS/part_export/www_shellcommands.pm190
-rw-r--r--FS/FS/part_export_option.pm134
-rw-r--r--FS/FS/part_pkg.pm896
-rw-r--r--FS/FS/part_pkg/base_delayed.pm51
-rw-r--r--FS/FS/part_pkg/base_rate.pm93
-rw-r--r--FS/FS/part_pkg/bulk.pm96
-rw-r--r--FS/FS/part_pkg/flat.pm168
-rw-r--r--FS/FS/part_pkg/flat_comission.pm66
-rw-r--r--FS/FS/part_pkg/flat_comission_cust.pm64
-rw-r--r--FS/FS/part_pkg/flat_comission_pkg.pm57
-rw-r--r--FS/FS/part_pkg/flat_delayed.pm68
-rw-r--r--FS/FS/part_pkg/flat_introrate.pm67
-rw-r--r--FS/FS/part_pkg/incomplete/billoneday.pm48
-rw-r--r--FS/FS/part_pkg/prepaid.pm38
-rw-r--r--FS/FS/part_pkg/prorate.pm122
-rw-r--r--FS/FS/part_pkg/prorate_delayed.pm61
-rw-r--r--FS/FS/part_pkg/sesmon_hour.pm56
-rw-r--r--FS/FS/part_pkg/sesmon_minute.pm55
-rw-r--r--FS/FS/part_pkg/sql_external.pm76
-rw-r--r--FS/FS/part_pkg/sql_generic.pm87
-rw-r--r--FS/FS/part_pkg/sqlradacct_hour.pm170
-rw-r--r--FS/FS/part_pkg/subscription.pm108
-rw-r--r--FS/FS/part_pkg/voip_cdr.pm375
-rw-r--r--FS/FS/part_pkg/voip_sqlradacct.pm192
-rw-r--r--FS/FS/part_pkg_option.pm131
-rw-r--r--FS/FS/part_pkg_taxclass.pm158
-rw-r--r--FS/FS/part_pop_local.pm113
-rw-r--r--FS/FS/part_referral.pm204
-rw-r--r--FS/FS/part_svc.pm825
-rw-r--r--FS/FS/part_svc_column.pm120
-rwxr-xr-xFS/FS/part_svc_router.pm33
-rwxr-xr-xFS/FS/part_virtual_field.pm301
-rw-r--r--FS/FS/pay_batch.pm538
-rw-r--r--FS/FS/payby.pm185
-rw-r--r--FS/FS/payinfo_Mixin.pm249
-rw-r--r--FS/FS/payment_gateway.pm200
-rw-r--r--FS/FS/payment_gateway_option.pm126
-rw-r--r--FS/FS/pkg_class.pm113
-rw-r--r--FS/FS/pkg_referral.pm126
-rw-r--r--FS/FS/pkg_svc.pm160
-rw-r--r--FS/FS/port.pm154
-rw-r--r--FS/FS/prepay_credit.pm202
-rw-r--r--FS/FS/queue.pm465
-rw-r--r--FS/FS/queue_arg.pm117
-rw-r--r--FS/FS/queue_depend.pm121
-rw-r--r--FS/FS/raddb.pm1912
-rw-r--r--FS/FS/radius_usergroup.pm131
-rw-r--r--FS/FS/rate.pm379
-rw-r--r--FS/FS/rate_detail.pm202
-rw-r--r--FS/FS/rate_prefix.pm139
-rw-r--r--FS/FS/rate_region.pm313
-rw-r--r--FS/FS/reason.pm184
-rw-r--r--FS/FS/reason_type.pm211
-rw-r--r--FS/FS/reg_code.pm223
-rw-r--r--FS/FS/reg_code_pkg.pm139
-rw-r--r--FS/FS/registrar.pm119
-rwxr-xr-xFS/FS/router.pm140
-rw-r--r--FS/FS/session.pm265
-rw-r--r--FS/FS/svc_Common.pm828
-rw-r--r--FS/FS/svc_External_Common.pm199
-rw-r--r--FS/FS/svc_Parent_Mixin.pm103
-rw-r--r--FS/FS/svc_acct.pm2664
-rw-r--r--FS/FS/svc_acct_pop.pm206
-rwxr-xr-xFS/FS/svc_broadband.pm301
-rw-r--r--FS/FS/svc_domain.pm478
-rw-r--r--FS/FS/svc_external.pm204
-rw-r--r--FS/FS/svc_forward.pm371
-rw-r--r--FS/FS/svc_phone.pm190
-rw-r--r--FS/FS/svc_www.pm312
-rw-r--r--FS/FS/type_pkgs.pm125
268 files changed, 0 insertions, 71594 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm
deleted file mode 100644
index 13dbd7f..0000000
--- a/FS/FS/AccessRight.pm
+++ /dev/null
@@ -1,295 +0,0 @@
-package FS::AccessRight;
-
-use strict;
-use vars qw(@rights); # %rights);
-use Tie::IxHash;
-
-=head1 NAME
-
-FS::AccessRight - Access control rights.
-
-=head1 SYNOPSIS
-
- use FS::AccessRight;
-
- my @rights = FS::AccessRight->rights;
-
- #my %rights = FS::AccessRight->rights_categorized;
- tie my %rights, 'Tie::IxHash', FS::AccessRight->rights_categorized;
- foreach my $category ( keys %rights ) {
- my @category_rights = @{ $rights{$category} };
- }
-
-=head1 DESCRIPTION
-
-Access control rights - Permission to perform specific actions that can be
-assigned to users and/or groups.
-
-=cut
-
-#@rights = (
-# 'Reports' => [
-# '_desc' => 'Access to high-level reporting',
-# ],
-# 'Configuration' => [
-# '_desc' => 'Access to configuration',
-#
-# 'Settings' => {},
-#
-# 'agent' => [
-# '_desc' => 'Master access to reseller configuration',
-# 'agent_type' => {},
-# 'agent' => {},
-# ],
-#
-# 'export_svc_pkg' => [
-# '_desc' => 'Access to export, service and package configuration',
-# 'part_export' => {},
-# 'part_svc' => {},
-# 'part_pkg' => {},
-# 'pkg_class' => {},
-# ],
-#
-# 'billing' => [
-# '_desc' => 'Access to billing configuration',
-# 'payment_gateway' => {},
-# 'part_bill_event' => {},
-# 'prepay_credit' => {},
-# 'rate' => {},
-# 'cust_main_county' => {},
-# ],
-#
-# 'dialup' => [
-# '_desc' => 'Access to dialup configuraiton',
-# 'svc_acct_pop' => {},
-# ],
-#
-# 'broadband' => [
-# '_desc' => 'Access to broadband configuration',
-# 'router' => {},
-# 'addr_block' => {},
-# ],
-#
-# 'misc' => [
-# 'part_referral' => {},
-# 'part_virtual_field' => {},
-# 'msgcat' => {},
-# 'inventory_class' => {},
-# ],
-#
-# },
-#
-#);
-#
-##turn it into a more hash-like structure, but ordered via IxHash
-
-#well, this is what we have for now. getting better.
-tie my %rights, 'Tie::IxHash',
-
- ###
- # basic customer rights
- ###
- 'Customer rights' => [
- 'New customer',
- 'View customer',
- #'View Customer | View tickets',
- 'Edit customer',
- 'Cancel customer',
- 'Complimentary customer', #aka users-allow_comp
- { rightname=>'Delete customer', desc=>"Enable customer deletions. Be very careful! Deleting a customer will remove all traces that this customer ever existed! It should probably only be used when auditing a legacy database. Normally, you cancel all of a customer's packages if they cancel service." }, #aka. deletecustomers
- 'Add customer note', #NEW
- 'Edit customer note', #NEW
- 'Bill customer now', #NEW
- ],
-
- ###
- # customer package rights
- ###
- 'Customer package rights' => [
- 'View customer packages', #NEW
- 'Order customer package',
- 'One-time charge',
- 'Change customer package',
- 'Bulk change customer packages',
- 'Edit customer package dates',
- 'Customize customer package',
- 'Suspend customer package',
- 'Suspend customer package later',
- 'Unsuspend customer package',
- 'Cancel customer package immediately',
- 'Cancel customer package later',
- 'Add on-the-fly cancel reason', #NEW
- 'Add on-the-fly suspend reason', #NEW
- ],
-
- ###
- # customer service rights
- ###
- 'Customer service rights' => [
- 'View customer services', #NEW
- 'Provision customer service',
- 'Recharge customer service', #NEW
- 'Unprovision customer service',
- 'Change customer service', #NEWNEW
- 'Edit usage', #NEW
- 'Edit home dir', #NEW
- 'Edit www config', #NEW
- 'Edit domain catchall', #NEW
- 'Edit domain nameservice', #NEW
-
- { rightname=>'View/link unlinked services', global=>1 }, #not agent-virtualizable without more work
- ],
-
- ###
- # customer invoice/financial info rights
- ###
- 'Customer invoice / financial info rights' => [
- 'View invoices',
- 'Resend invoices', #NEWNEW
- 'View customer tax exemptions', #yow
- 'View customer batched payments', #NEW
- 'View customer billing events', #NEW
- ],
-
- ###
- # customer payment rights
- ###
- 'Customer payment rights' => [
- 'Post payment',
- 'Post payment batch',
- 'Apply payment', #NEWNEW
- { rightname=>'Unapply payment', desc=>'Enable "unapplication" of unclosed payments from specific invoices.' }, #aka. unapplypayments
- 'Process payment',
- 'Refund payment',
-
- { rightname=>'Delete payment', desc=>'Enable deletion of unclosed payments. Be very careful! Only delete payments that were data-entry errors, not adjustments.' }, #aka. deletepayments Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.
-
- ],
-
- ###
- # customer credit rights
- ###
- 'Customer credit and refund rights' => [
- 'Post credit',
- 'Apply credit', #NEWNEW
- { rightname=>'Unapply credit', desc=>'Enable "unapplication" of unclosed credits.' }, #aka unapplycredits
- { rightname=>'Delete credit', desc=>'Enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments.' }, #aka. deletecredits Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.
- 'Delete refund', #NEW
- 'Add on-the-fly credit reason', #NEW
- ],
-
- ###
- # customer voiding rights..
- ###
- 'Customer void rights' => [
- { rightname=>'Credit card void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. cc-void
- { rightname=>'Echeck void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. echeck-void
- 'Regular void',
- { rightname=>'Unvoid', desc=>'Enable unvoiding of voided payments' }, #aka. unvoid
-
-
- ],
-
- ###
- # report/listing rights...
- ###
- 'Reprting/listing rights' => [
- 'List customers',
- 'List zip codes', #NEW
- 'List invoices',
- 'List packages',
- 'List services',
-
- { rightname=> 'List rating data', desc=>'Usage reports', global=>1 },
- 'Billing event reports',
- 'Financial reports',
- ],
-
- ###
- # misc rights
- ###
- 'Miscellaneous rights' => [
- { rightname=>'Job queue', global=>1 },
- { rightname=>'Time queue', global=>1 },
- { rightname=>'Process batches', global=>1 },
- { rightname=>'Reprocess batches', global=>1 },
- { rightname=>'Import', global=>1 }, #some of these are ag-virt'ed now? give em their own ACLs
- { rightname=>'Export', global=>1 },
- #],
- #
- ###
- # misc misc rights
- ###
- #'Database access rights' => [
- { rightname=>'Raw SQL', global=>1 }, #NEW
- ],
-
- ###
- # setup/config rights
- ###
- 'Configuration rights' => [
- 'Edit advertising sources',
- { rightname=>'Edit global advertising sources', global=>1 },
-
- 'Edit package definitions',
- { rightname=>'Edit global package definitions', global=>1 },
-
- 'Edit billing events',
- { rightname=>'Edit global billing events', global=>1 },
-
- { rightname=>'Configuration', global=>1 }, #most of the rest of the configuraiton is not agent-virtualized
- ],
-
-;
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item rights
-
-Returns a list of right names.
-
-=cut
-
- sub rights {
- #my $class = shift;
- map { ref($_) ? $_->{'rightname'} : $_ } map @{ $rights{$_} }, keys %rights;
- }
-
-=item rights_info
-
-Returns a list of key-value pairs suitable for assigning to a hash. Keys are
-category names and values are list references of rights. Each element of the
-list reference scalar right name or a hashref with the following keys:
-
-=over 4
-
-=item rightname - Right name
-
-=item desc - Extended right description
-
-=item global - Global flag, indicates that this access right provides access to global data which is shared among all agents.
-
-=back
-
-=cut
-
-sub rights_info {
- %rights;
-}
-
-=back
-
-=head1 BUGS
-
-Damn those infernal six-legged creatures!
-
-=head1 SEE ALSO
-
-L<FS::access_right>, L<FS::access_group>, L<FS::access_user>
-
-=cut
-
-1;
-
diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm
deleted file mode 100644
index 38a869c..0000000
--- a/FS/FS/CGI.pm
+++ /dev/null
@@ -1,425 +0,0 @@
-package FS::CGI;
-
-use strict;
-use vars qw(@EXPORT_OK @ISA);
-use Exporter;
-use CGI;
-use URI::URL;
-#use CGI::Carp qw(fatalsToBrowser);
-use FS::UID;
-
-@ISA = qw(Exporter);
-@EXPORT_OK = qw(header menubar idiot eidiot popurl rooturl table itable ntable
- small_custview myexit http_header);
-
-=head1 NAME
-
-FS::CGI - Subroutines for the web interface
-
-=head1 SYNOPSIS
-
- use FS::CGI qw(header menubar idiot eidiot popurl);
-
- print header( 'Title', '' );
- print header( 'Title', menubar('item', 'URL', ... ) );
-
- idiot "error message";
- eidiot "error message";
-
- $url = popurl; #returns current url
- $url = popurl(3); #three levels up
-
-=head1 DESCRIPTION
-
-Provides a few common subroutines for the web interface.
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item header TITLE, MENUBAR
-
-Returns an HTML header.
-
-=cut
-
-sub header {
- use Carp;
- carp 'FS::CGI::header deprecated; include /elements/header.html instead';
-
- my($title,$menubar,$etc)=@_; #$etc is for things like onLoad= etc.
- $etc = '' unless defined $etc;
-
- my $x = <<END;
- <HTML>
- <HEAD>
- <TITLE>
- $title
- </TITLE>
- <META HTTP-Equiv="Cache-Control" Content="no-cache">
- <META HTTP-Equiv="Pragma" Content="no-cache">
- <META HTTP-Equiv="Expires" Content="0">
- </HEAD>
- <BODY BGCOLOR="#e8e8e8"$etc>
- <FONT SIZE=6>
- <CENTER>$title</CENTER>
- </FONT>
- <BR><!--<BR>-->
-END
- $x .= $menubar. "<BR><BR>" if $menubar;
- $x;
-}
-
-=item http_header
-
-Sets an http header.
-
-=cut
-
-sub http_header {
- my ( $header, $value ) = @_;
- if (exists $ENV{MOD_PERL}) {
- if ( defined $HTML::Mason::Commands::r ) { #Mason
- ## is this the correct pacakge for $r ??? for 1.0x and 1.1x ?
- if ( $header =~ /^Content-Type$/ ) {
- $HTML::Mason::Commands::r->content_type($value);
- } else {
- $HTML::Mason::Commands::r->header_out( $header => $value );
- }
- } else {
- die "http_header called in unknown environment";
- }
- } else {
- die "http_header called not running under mod_perl";
- }
-
-}
-
-=item menubar ITEM, URL, ...
-
-Returns an HTML menubar.
-
-=cut
-
-sub menubar { #$menubar=menubar('Main Menu', '../', 'Item', 'url', ... );
- use Carp;
- carp 'FS::CGI::menubar deprecated; include /elements/menubar.html instead';
-
- my($item,$url,@html);
- while (@_) {
- ($item,$url)=splice(@_,0,2);
- next if $item =~ /^\s*Main\s+Menu\s*$/i;
- push @html, qq!<A HREF="$url">$item</A>!;
- }
- join(' | ',@html);
-}
-
-=item idiot ERROR
-
-This is depriciated. Don't use it.
-
-Sends an HTML error message.
-
-=cut
-
-sub idiot {
- #warn "idiot depriciated";
- my($error)=@_;
-# my $cgi = &FS::UID::cgi();
-# if ( $cgi->isa('CGI::Base') ) {
-# no strict 'subs';
-# &CGI::Base::SendHeaders;
-# } else {
-# print $cgi->header( @FS::CGI::header );
-# }
- print <<END;
-<HTML>
- <HEAD>
- <TITLE>Error processing your request</TITLE>
- <META HTTP-Equiv="Cache-Control" Content="no-cache">
- <META HTTP-Equiv="Pragma" Content="no-cache">
- <META HTTP-Equiv="Expires" Content="0">
- </HEAD>
- <BODY>
- <CENTER>
- <H4>Error processing your request</H4>
- </CENTER>
- Your request could not be processed because of the following error:
- <P><B>$error</B>
- </BODY>
-</HTML>
-END
-
-}
-
-=item eidiot ERROR
-
-This is depriciated. Don't use it.
-
-Sends an HTML error message, then exits.
-
-=cut
-
-sub eidiot {
- warn "eidiot depriciated";
- $HTML::Mason::Commands::r->send_http_header
- if defined $HTML::Mason::Commands::r;
- idiot(@_);
- &myexit();
-}
-
-=item myexit
-
-You probably shouldn't use this; but if you must:
-
-If running under mod_perl, calles Apache::exit, otherwise, calls exit.
-
-=cut
-
-sub myexit {
- if (exists $ENV{MOD_PERL}) {
-
- if ( defined $HTML::Mason::Commands::m ) { #Mason
- #$HTML::Mason::Commands::m->flush_buffer();
- $HTML::Mason::Commands::m->abort();
- die "shouldn't fall through to here (mason \$m->abort didn't)";
- } else {
- #??? well, it is $ENV{MOD_PERL}
- warn "running under unknown mod_perl environment; trying Apache::exit()";
- require Apache;
- Apache::exit();
- }
- } else {
- exit;
- }
-}
-
-=item popurl LEVEL
-
-Returns current URL with LEVEL levels of path removed from the end (default 0).
-
-=cut
-
-sub popurl {
- my($up)=@_;
- my $cgi = &FS::UID::cgi;
- my $url_string = $cgi->isa('Apache') ? $cgi->uri : $cgi->url;
- $url_string =~ s/\?.*//;
- my $url = new URI::URL ( $url_string );
- my(@path)=$url->path_components;
- splice @path, 0-$up;
- $url->path_components(@path);
- my $x = $url->as_string;
- $x .= '/' unless $x =~ /\/$/;
- $x;
-}
-
-=item rooturl
-
-=cut
-
-sub rooturl {
- # better to start with the client-provided URL
- my $cgi = &FS::UID::cgi;
- my $url_string = $cgi->isa('Apache') ? $cgi->uri : $cgi->url;
- $url_string =~ s/\?.*//;
-
- #even though this is kludgy
- $url_string =~ s{ / index\.html /? $ }
- {/}x;
- $url_string =~
- s{
- /
- (browse|config|docs|edit|graph|misc|search|view|pref|rt|elements)
- /
- (process/)?
- ([\w\-\.\/]+)
- $
- }
- {}x;
-
- #elements because of progress-popup.html...
- #XXX remove anything from elements that is called directly & prevent
- #those pages from being served up
-
- $url_string .= '/' unless $url_string =~ /\/$/;
-
- $url_string;
-
-}
-
-=item table
-
-Returns HTML tag for beginning a table.
-
-=cut
-
-sub table {
- use Carp;
- carp 'FS::CGI::table deprecated; include /elements/table.html instead';
-
- my $col = shift;
- if ( $col ) {
- qq!<TABLE BGCOLOR="$col" BORDER=1 WIDTH="100%" CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">!;
- } else {
- '<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">';
- }
-}
-
-=item itable
-
-Returns HTML tag for beginning an (invisible) table.
-
-=cut
-
-sub itable {
- my $col = shift;
- my $cellspacing = shift || 0;
- my $width = ( scalar(@_) && shift ) ? '' : 'WIDTH="100%"'; #bah
- if ( $col ) {
- qq!<TABLE BGCOLOR="$col" BORDER=0 CELLSPACING=$cellspacing $width>!;
- } else {
- qq!<TABLE BORDER=0 CELLSPACING=$cellspacing $width>!;
- }
-}
-
-=item ntable
-
-This is getting silly.
-
-=cut
-
-sub ntable {
- my $col = shift;
- my $cellspacing = shift || 0;
- if ( $col ) {
- qq!<TABLE BGCOLOR="$col" BORDER=0 CELLSPACING=$cellspacing>!;
- } else {
- '<TABLE BORDER CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">';
- }
-
-}
-
-=item small_custview CUSTNUM || CUST_MAIN_OBJECT, COUNTRYDEFAULT, NOBALANCE_FLAG, URL
-
-Sheesh. I should just switch to Mason.
-
-=cut
-
-sub small_custview {
- use FS::Record qw(qsearchs);
- use FS::cust_main;
-
- my $arg = shift;
- my $countrydefault = shift || 'US';
- my $nobalance = shift;
- my $url = shift;
-
- my $cust_main = ref($arg) ? $arg
- : qsearchs('cust_main', { 'custnum' => $arg } )
- or die "unknown custnum $arg";
-
- my $html;
-
- $html = qq!View <A HREF="$url?! . $cust_main->custnum . '">'
- if $url;
-
- $html .= 'Customer #<B>'. $cust_main->custnum. '</B></A>'.
- ' - <B><FONT COLOR="#'. $cust_main->statuscolor. '">'.
- ucfirst($cust_main->status). '</FONT></B>'.
- ntable('#e8e8e8'). '<TR><TD VALIGN="top">'. ntable("#cccccc",2).
- '<TR><TD ALIGN="right" VALIGN="top">Billing<BR>Address</TD><TD BGCOLOR="#ffffff">'.
- $cust_main->getfield('last'). ', '. $cust_main->first. '<BR>';
-
- $html .= $cust_main->company. '<BR>' if $cust_main->company;
- $html .= $cust_main->address1. '<BR>';
- $html .= $cust_main->address2. '<BR>' if $cust_main->address2;
- $html .= $cust_main->city. ', '. $cust_main->state. ' '. $cust_main->zip. '<BR>';
- $html .= $cust_main->country. '<BR>'
- if $cust_main->country && $cust_main->country ne $countrydefault;
-
- $html .= '</TD></TR><TR><TD></TD><TD BGCOLOR="#ffffff">';
- if ( $cust_main->daytime && $cust_main->night ) {
- use FS::Msgcat;
- $html .= ( FS::Msgcat::_gettext('daytime') || 'Day' ).
- ' '. $cust_main->daytime.
- '<BR>'. ( FS::Msgcat::_gettext('night') || 'Night' ).
- ' '. $cust_main->night;
- } elsif ( $cust_main->daytime || $cust_main->night ) {
- $html .= $cust_main->daytime || $cust_main->night;
- }
- if ( $cust_main->fax ) {
- $html .= '<BR>Fax '. $cust_main->fax;
- }
-
- $html .= '</TD></TR></TABLE></TD>';
-
- if ( defined $cust_main->dbdef_table->column('ship_last') ) {
-
- my $pre = $cust_main->ship_last ? 'ship_' : '';
-
- $html .= '<TD VALIGN="top">'. ntable("#cccccc",2).
- '<TR><TD ALIGN="right" VALIGN="top">Service<BR>Address</TD><TD BGCOLOR="#ffffff">'.
- $cust_main->get("${pre}last"). ', '.
- $cust_main->get("${pre}first"). '<BR>';
- $html .= $cust_main->get("${pre}company"). '<BR>'
- if $cust_main->get("${pre}company");
- $html .= $cust_main->get("${pre}address1"). '<BR>';
- $html .= $cust_main->get("${pre}address2"). '<BR>'
- if $cust_main->get("${pre}address2");
- $html .= $cust_main->get("${pre}city"). ', '.
- $cust_main->get("${pre}state"). ' '.
- $cust_main->get("${pre}ship_zip"). '<BR>';
- $html .= $cust_main->get("${pre}country"). '<BR>'
- if $cust_main->get("${pre}country")
- && $cust_main->get("${pre}country") ne $countrydefault;
-
- $html .= '</TD></TR><TR><TD></TD><TD BGCOLOR="#ffffff">';
-
- if ( $cust_main->get("${pre}daytime") && $cust_main->get("${pre}night") ) {
- use FS::Msgcat;
- $html .= ( FS::Msgcat::_gettext('daytime') || 'Day' ).
- ' '. $cust_main->get("${pre}daytime").
- '<BR>'. ( FS::Msgcat::_gettext('night') || 'Night' ).
- ' '. $cust_main->get("${pre}night");
- } elsif ( $cust_main->get("${pre}daytime")
- || $cust_main->get("${pre}night") ) {
- $html .= $cust_main->get("${pre}daytime")
- || $cust_main->get("${pre}night");
- }
- if ( $cust_main->get("${pre}fax") ) {
- $html .= '<BR>Fax '. $cust_main->get("${pre}fax");
- }
-
- $html .= '</TD></TR></TABLE></TD>';
- }
-
- $html .= '</TR></TABLE>';
-
- $html .= '<BR>Balance: <B>$'. $cust_main->balance. '</B><BR>'
- unless $nobalance;
-
- # last payment might be good here too?
-
- $html;
-}
-
-=back
-
-=head1 BUGS
-
-Not OO.
-
-Not complete.
-
-small_custview sooooo doesn't belong here. i should just switch to Mason.
-
-=head1 SEE ALSO
-
-L<CGI>, L<CGI::Base>
-
-=cut
-
-1;
-
-
diff --git a/FS/FS/ClientAPI.pm b/FS/FS/ClientAPI.pm
deleted file mode 100644
index 902f58b..0000000
--- a/FS/FS/ClientAPI.pm
+++ /dev/null
@@ -1,37 +0,0 @@
-package FS::ClientAPI;
-
-use strict;
-use vars qw(%handler $domain $DEBUG);
-
-$DEBUG = 0;
-
-%handler = ();
-
-#find modules
-foreach my $INC ( @INC ) {
- my $glob = "$INC/FS/ClientAPI/*.pm";
- warn "FS::ClientAPI: searching $glob" if $DEBUG;
- foreach my $file ( glob($glob) ) {
- $file =~ /\/(\w+)\.pm$/ or do {
- warn "unrecognized ClientAPI file: $file";
- next
- };
- my $mod = $1;
- warn "using FS::ClientAPI::$mod" if $DEBUG;
- eval "use FS::ClientAPI::$mod;";
- die "error using FS::ClientAPI::$mod: $@" if $@;
- }
-}
-
-#---
-
-sub dispatch {
- my ( $self, $name ) = ( shift, shift );
- $name =~ s(/)(::)g;
- my $sub = "FS::ClientAPI::$name";
- no strict 'refs';
- &{$sub}(@_);
-}
-
-1;
-
diff --git a/FS/FS/ClientAPI/Agent.pm b/FS/FS/ClientAPI/Agent.pm
deleted file mode 100644
index daede59..0000000
--- a/FS/FS/ClientAPI/Agent.pm
+++ /dev/null
@@ -1,125 +0,0 @@
-package FS::ClientAPI::Agent;
-
-#some false laziness w/MyAccount
-
-use strict;
-use vars qw($cache);
-use subs qw(_cache);
-use Digest::MD5 qw(md5_hex);
-use FS::Record qw(qsearchs); # qsearch dbdef dbh);
-use FS::ClientAPI_SessionCache;
-use FS::agent;
-use FS::cust_main qw(smart_search);
-
-sub _cache {
- $cache ||= new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::Agent',
- } );
-}
-
-sub agent_login {
- my $p = shift;
-
- #don't allow a blank login to first unconfigured agent with no user/pass
- return { error => 'Must specify your reseller username and password.' }
- unless length($p->{'username'}) && length($p->{'password'});
-
- my $agent = qsearchs( 'agent', {
- 'username' => $p->{'username'},
- '_password' => $p->{'password'},
- } );
-
- unless ( $agent ) { return { error => 'Incorrect password.' } }
-
- my $session = {
- 'agentnum' => $agent->agentnum,
- 'agent' => $agent->agent,
- };
-
- my $session_id;
- do {
- $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
- } until ( ! defined _cache->get($session_id) ); #just in case
-
- _cache->set( $session_id, $session, '1 hour' );
-
- { 'error' => '',
- 'session_id' => $session_id,
- };
-}
-
-sub agent_logout {
- my $p = shift;
- if ( $p->{'session_id'} ) {
- _cache->remove($p->{'session_id'});
- return { 'error' => '' };
- } else {
- return { 'error' => "Can't resume session" }; #better error message
- }
-}
-
-sub agent_info {
- my $p = shift;
-
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- #my %return;
-
- my $agentnum = $session->{'agentnum'};
-
- my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } )
- or return { 'error' => "unknown agentnum $agentnum" };
-
- { 'error' => '',
- 'agentnum' => $agentnum,
- 'agent' => $agent->agent,
- 'num_prospect' => $agent->num_prospect_cust_main,
- 'num_active' => $agent->num_active_cust_main,
- 'num_susp' => $agent->num_susp_cust_main,
- 'num_cancel' => $agent->num_cancel_cust_main,
- #%return,
- };
-
-}
-
-sub agent_list_customers {
- my $p = shift;
-
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- #my %return;
-
- my $agentnum = $session->{'agentnum'};
-
- my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } )
- or return { 'error' => "unknown agentnum $agentnum" };
-
- my @cust_main = smart_search( 'search' => $p->{'search'},
- 'agentnum' => $agentnum,
- );
-
- #aggregate searches
- push @cust_main,
- map $agent->$_(), map $_.'_cust_main',
- grep $p->{$_}, qw( prospect active susp cancel );
-
- #eliminate dups?
- my %saw = ();
- @cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
-
- { customers => [ map {
- my $cust_main = $_;
- my $hashref = $cust_main->hashref;
- $hashref->{$_} = $cust_main->$_()
- foreach qw(name status statuscolor);
- delete $hashref->{$_} foreach qw( payinfo paycvv );
- $hashref;
- } @cust_main
- ],
- }
-
-}
-
-1;
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
deleted file mode 100644
index 2d39510..0000000
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ /dev/null
@@ -1,1291 +0,0 @@
-package FS::ClientAPI::MyAccount;
-
-use strict;
-use vars qw($cache);
-use subs qw(_cache);
-use Digest::MD5 qw(md5_hex);
-use Date::Format;
-use Business::CreditCard;
-use Time::Duration;
-use FS::CGI qw(small_custview); #doh
-use FS::UI::Web;
-use FS::UI::bytecount;
-use FS::Conf;
-use FS::Record qw(qsearch qsearchs);
-use FS::Msgcat qw(gettext);
-use FS::Misc qw(card_types);
-use FS::ClientAPI_SessionCache;
-use FS::svc_acct;
-use FS::svc_domain;
-use FS::svc_external;
-use FS::part_svc;
-use FS::cust_main;
-use FS::cust_bill;
-use FS::cust_main_county;
-use FS::cust_pkg;
-use FS::payby;
-use FS::acct_rt_transaction;
-use HTML::Entities;
-
-#false laziness with FS::cust_main
-BEGIN {
- eval "use Time::Local;";
- die "Time::Local minimum version 1.05 required with Perl versions before 5.6"
- if $] < 5.006 && !defined($Time::Local::VERSION);
- eval "use Time::Local qw(timelocal_nocheck);";
-}
-
-use vars qw( @cust_main_editable_fields );
-@cust_main_editable_fields = qw(
- first last company address1 address2 city
- county state zip country daytime night fax
- ship_first ship_last ship_company ship_address1 ship_address2 ship_city
- ship_state ship_zip ship_country ship_daytime ship_night ship_fax
- payby payinfo payname paystart_month paystart_year payissue payip
- ss paytype paystate stateid stateid_state
-);
-
-use subs qw(_provision);
-
-sub _cache {
- $cache ||= new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::MyAccount',
- } );
-}
-
-#false laziness w/FS::ClientAPI::passwd::passwd
-sub login {
- my $p = shift;
-
- my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } )
- or return { error => 'Domain '. $p->{'domain'}. ' not found' };
-
- my $svc_acct = qsearchs( 'svc_acct', { 'username' => $p->{'username'},
- 'domsvc' => $svc_domain->svcnum, }
- );
- return { error => 'User not found.' } unless $svc_acct;
-
- my $conf = new FS::Conf;
- my $pkg_svc = $svc_acct->cust_svc->pkg_svc;
- return { error => 'Only primary user may log in.' }
- if $conf->exists('selfservice_server-primary_only')
- && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' );
-
- return { error => 'Incorrect password.' }
- unless $svc_acct->check_password($p->{'password'});
-
- my $session = {
- 'svcnum' => $svc_acct->svcnum,
- };
-
- my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
- if ( $cust_pkg ) {
- my $cust_main = $cust_pkg->cust_main;
- $session->{'custnum'} = $cust_main->custnum;
- }
-
- my $session_id;
- do {
- $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
- } until ( ! defined _cache->get($session_id) ); #just in case
-
- my $timeout = $conf->config('selfservice-session_timeout') || '1 hour';
- _cache->set( $session_id, $session, $timeout );
-
- return { 'error' => '',
- 'session_id' => $session_id,
- };
-}
-
-sub logout {
- my $p = shift;
- if ( $p->{'session_id'} ) {
- _cache->remove($p->{'session_id'});
- return { 'error' => '' };
- } else {
- return { 'error' => "Can't resume session" }; #better error message
- }
-}
-
-sub customer_info {
- my $p = shift;
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my %return;
-
- my $conf = new FS::Conf;
- if ($conf->exists('cust_main-require_address2')) {
- $return{'require_address2'} = '1';
- }else{
- $return{'require_address2'} = '';
- }
-
- if ( $custnum ) { #customer record
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- $return{balance} = $cust_main->balance;
-
- $return{tickets} = [ ($cust_main->tickets) ];
-
- my @open = map {
- {
- invnum => $_->invnum,
- date => time2str("%b %o, %Y", $_->_date),
- owed => $_->owed,
- };
- } $cust_main->open_cust_bill;
- $return{open_invoices} = \@open;
-
- $return{small_custview} =
- small_custview( $cust_main, $conf->config('countrydefault') );
-
- $return{name} = $cust_main->first. ' '. $cust_main->get('last');
-
- for (@cust_main_editable_fields) {
- $return{$_} = $cust_main->get($_);
- }
-
- if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
- $return{payinfo} = $cust_main->paymask;
- @return{'month', 'year'} = $cust_main->paydate_monthyear;
- }
-
- $return{'invoicing_list'} =
- join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
- $return{'postal_invoicing'} =
- 0 < ( grep { $_ eq 'POST' } $cust_main->invoicing_list );
-
- if (scalar($conf->config('support_packages'))) {
- my @support_services = ();
- foreach ($cust_main->support_services) {
- my $seconds = $_->svc_x->seconds;
- my $time_remaining = (($seconds < 0) ? '-' : '' ).
- int(abs($seconds)/3600)."h".
- sprintf("%02d",(abs($seconds)%3600)/60)."m";
- my $cust_pkg = $_->cust_pkg;
- my $pkgnum = '';
- my $pkg = '';
- $pkgnum = $cust_pkg->pkgnum if $cust_pkg;
- $pkg = $cust_pkg->part_pkg->pkg if $cust_pkg;
- push @support_services, { svcnum => $_->svcnum,
- time => $time_remaining,
- pkgnum => $pkgnum,
- pkg => $pkg,
- };
- }
- $return{support_services} = \@support_services;
- }
-
- } elsif ( $session->{'svcnum'} ) { #no customer record
-
- my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
- or die "unknown svcnum";
- $return{name} = $svc_acct->email;
-
- } else {
-
- return { 'error' => 'Expired session' }; #XXX redirect to login w/this err!
-
- }
-
- return { 'error' => '',
- 'custnum' => $custnum,
- %return,
- };
-
-}
-
-sub edit_info {
- my $p = shift;
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- my $custnum = $session->{'custnum'}
- or return { 'error' => "no customer record" };
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
- or return { 'error' => "unknown custnum $custnum" };
-
- my $new = new FS::cust_main { $cust_main->hash };
- $new->set( $_ => $p->{$_} )
- foreach grep { exists $p->{$_} } @cust_main_editable_fields;
-
- my $payby = '';
- if (exists($p->{'payby'})) {
- $p->{'payby'} =~ /^([A-Z]{4})$/
- or return { 'error' => "illegal_payby " . $p->{'payby'} };
- $payby = $1;
- }
-
- if ( $payby =~ /^(CARD|DCRD)$/ ) {
-
- $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01');
-
- if ( $new->payinfo eq $cust_main->paymask ) {
- $new->payinfo($cust_main->payinfo);
- } else {
- $new->payinfo($p->{'payinfo'});
- }
-
- $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
-
- }elsif ( $payby =~ /^(CHEK|DCHK)$/ ) {
- my $payinfo;
- $p->{'payinfo1'} =~ /^([\dx]+)$/
- or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
- my $payinfo1 = $1;
- $p->{'payinfo2'} =~ /^([\dx]+)$/
- or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
- my $payinfo2 = $1;
- $payinfo = $payinfo1. '@'. $payinfo2;
-
- if ( $payinfo eq $cust_main->paymask ) {
- $new->payinfo($cust_main->payinfo);
- } else {
- $new->payinfo($payinfo);
- }
-
- $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
-
- }elsif ( $payby =~ /^(BILL)$/ ) {
- } elsif ( $payby ) { #notyet ready
- return { 'error' => "unknown payby $payby" };
- }
-
- my @invoicing_list;
- if ( exists $p->{'invoicing_list'} || exists $p->{'postal_invoicing'} ) {
- #false laziness with httemplate/edit/process/cust_main.cgi
- @invoicing_list = split( /\s*\,\s*/, $p->{'invoicing_list'} );
- push @invoicing_list, 'POST' if $p->{'postal_invoicing'};
- } else {
- @invoicing_list = $cust_main->invoicing_list;
- }
-
- my $error = $new->replace($cust_main, \@invoicing_list);
- return { 'error' => $error } if $error;
- #$cust_main = $new;
-
- return { 'error' => '' };
-}
-
-sub payment_info {
- my $p = shift;
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- ##
- #generic
- ##
-
- use vars qw($payment_info); #cache for performance
- unless ( $payment_info ) {
-
- my $conf = new FS::Conf;
- my %states = map { $_->state => 1 }
- qsearch('cust_main_county', {
- 'country' => $conf->config('countrydefault') || 'US'
- } );
-
- $payment_info = {
-
- #list all counties/states/countries
- 'cust_main_county' =>
- [ map { $_->hashref } qsearch('cust_main_county', {}) ],
-
- #shortcut for one-country folks
- 'states' =>
- [ sort { $a cmp $b } keys %states ],
-
- 'card_types' => card_types(),
-
- 'paytypes' => [ @FS::cust_main::paytypes ],
-
- 'paybys' => [ $conf->config('signup_server-payby') ],
-
- 'stateid_label' => FS::Msgcat::_gettext('stateid'),
- 'stateid_state_label' => FS::Msgcat::_gettext('stateid_state'),
-
- 'show_ss' => $conf->exists('show_ss'),
- 'show_stateid' => $conf->exists('show_stateid'),
- 'show_paystate' => $conf->exists('show_bankstate'),
- };
-
- }
-
- ##
- #customer-specific
- ##
-
- my %return = %$payment_info;
-
- my $custnum = $session->{'custnum'};
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
- or return { 'error' => "unknown custnum $custnum" };
-
- $return{balance} = $cust_main->balance;
-
- $return{payname} = $cust_main->payname
- || ( $cust_main->first. ' '. $cust_main->get('last') );
-
- $return{$_} = $cust_main->get($_) for qw(address1 address2 city state zip);
-
- $return{payby} = $cust_main->payby;
- $return{stateid_state} = $cust_main->stateid_state;
-
- if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
- $return{card_type} = cardtype($cust_main->payinfo);
- $return{payinfo} = $cust_main->paymask;
-
- @return{'month', 'year'} = $cust_main->paydate_monthyear;
-
- }
-
- if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
- my ($payinfo1, $payinfo2) = split '@', $cust_main->paymask;
- $return{payinfo1} = $payinfo1;
- $return{payinfo2} = $payinfo2;
- $return{paytype} = $cust_main->paytype;
- $return{paystate} = $cust_main->paystate;
-
- }
-
- #doubleclick protection
- my $_date = time;
- $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
-
- return { 'error' => '',
- %return,
- };
-
-};
-
-#some false laziness with httemplate/process/payment.cgi - look there for
-#ACH and CVV support stuff
-sub process_payment {
-
- my $p = shift;
-
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- my %return;
-
- my $custnum = $session->{'custnum'};
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
- or return { 'error' => "unknown custnum $custnum" };
-
- $p->{'payname'} =~ /^([\w \,\.\-\']+)$/
- or return { 'error' => gettext('illegal_name'). " payname: ". $p->{'payname'} };
- my $payname = $1;
-
- $p->{'paybatch'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
- or return { 'error' => gettext('illegal_text'). " paybatch: ". $p->{'paybatch'} };
- my $paybatch = $1;
-
- $p->{'payby'} =~ /^([A-Z]{4})$/
- or return { 'error' => "illegal_payby " . $p->{'payby'} };
- my $payby = $1;
-
- my $payinfo;
- my $paycvv = '';
- if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
-
- $p->{'payinfo1'} =~ /^([\dx]+)$/
- or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
- my $payinfo1 = $1;
- $p->{'payinfo2'} =~ /^([\dx]+)$/
- or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
- my $payinfo2 = $1;
- $payinfo = $payinfo1. '@'. $payinfo2;
-
- $payinfo = $cust_main->payinfo
- if $cust_main->paymask eq $payinfo;
-
- } elsif ( $payby eq 'CARD' || $payby eq 'DCRD' ) {
-
- $payinfo = $p->{'payinfo'};
- $payinfo =~ s/[^\dx]//g;
- $payinfo =~ /^(\d{13,16})$/
- or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
- $payinfo = $1;
-
- $payinfo = $cust_main->payinfo
- if $cust_main->paymask eq $payinfo;
-
- validate($payinfo)
- or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
- return { 'error' => gettext('unknown_card_type') }
- if cardtype($payinfo) eq "Unknown";
-
- if ( length($p->{'paycvv'}) && $p->{'paycvv'} !~ /^\s*$/ ) {
- if ( cardtype($payinfo) eq 'American Express card' ) {
- $p->{'paycvv'} =~ /^\s*(\d{4})\s*$/
- or return { 'error' => "CVV2 (CID) for American Express cards is four digits." };
- $paycvv = $1;
- } else {
- $p->{'paycvv'} =~ /^\s*(\d{3})\s*$/
- or return { 'error' => "CVV2 (CVC2/CID) is three digits." };
- $paycvv = $1;
- }
- }
-
- } else {
- die "unknown payby $payby";
- }
-
- my %payby2fields = (
- 'CARD' => [ qw( paystart_month paystart_year payissue address1 address2 city state zip payip ) ],
- 'CHEK' => [ qw( ss paytype paystate stateid stateid_state payip ) ],
- );
-
- my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $p->{'amount'},
- 'quiet' => 1,
- 'payinfo' => $payinfo,
- 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01',
- 'payname' => $payname,
- 'paybatch' => $paybatch,
- 'paycvv' => $paycvv,
- map { $_ => $p->{$_} } @{ $payby2fields{$payby} }
- );
- return { 'error' => $error } if $error;
-
- $cust_main->apply_payments;
-
- if ( $p->{'save'} ) {
- my $new = new FS::cust_main { $cust_main->hash };
- if ($payby eq 'CARD' || $payby eq 'DCRD') {
- $new->set( $_ => $p->{$_} )
- foreach qw( payname paystart_month paystart_year payissue payip
- address1 address2 city state zip payinfo );
- $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
- } elsif ($payby eq 'CHEK' || $payby eq 'DCHK') {
- $new->set( $_ => $p->{$_} )
- foreach qw( payname payip paytype paystate
- stateid stateid_state );
- $new->set( 'payinfo' => $payinfo );
- $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
- }
- $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
- my $error = $new->replace($cust_main);
- return { 'error' => $error } if $error;
- $cust_main = $new;
- }
-
- return { 'error' => '' };
-
-}
-
-sub process_payment_order_pkg {
- my $p = shift;
-
- my $hr = process_payment($p);
- return $hr if $hr->{'error'};
-
- order_pkg($p);
-}
-
-sub process_prepay {
-
- my $p = shift;
-
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- my %return;
-
- my $custnum = $session->{'custnum'};
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
- or return { 'error' => "unknown custnum $custnum" };
-
- my( $amount, $seconds, $upbytes, $downbytes, $totalbytes ) = ( 0, 0, 0, 0, 0 );
- my $error = $cust_main->recharge_prepay( $p->{'prepaid_cardnum'},
- \$amount,
- \$seconds,
- \$upbytes,
- \$downbytes,
- \$totalbytes,
- );
-
- return { 'error' => $error } if $error;
-
- return { 'error' => '',
- 'amount' => $amount,
- 'seconds' => $seconds,
- 'duration' => duration_exact($seconds),
- 'upbytes' => $upbytes,
- 'upload' => FS::UI::bytecount::bytecount_unexact($upbytes),
- 'downbytes' => $downbytes,
- 'download' => FS::UI::bytecount::bytecount_unexact($downbytes),
- 'totalbytes'=> $totalbytes,
- 'totalload' => FS::UI::bytecount::bytecount_unexact($totalbytes),
- };
-
-}
-
-sub invoice {
- my $p = shift;
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- my $custnum = $session->{'custnum'};
-
- my $invnum = $p->{'invnum'};
-
- my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum,
- 'custnum' => $custnum } )
- or return { 'error' => "Can't find invnum" };
-
- #my %return;
-
- return { 'error' => '',
- 'invnum' => $invnum,
- 'invoice_text' => join('', $cust_bill->print_text ),
- 'invoice_html' => $cust_bill->print_html,
- };
-
-}
-
-sub invoice_logo {
- my $p = shift;
-
- #sessioning for this? how do we get the session id to the backend invoice
- # template so it can add it to the link, blah
-
- my $templatename = $p->{'templatename'};
-
- #false laziness-ish w/view/cust_bill-logo.cgi
-
- my $conf = new FS::Conf;
- if ( $templatename =~ /^([^\.\/]*)$/ && $conf->exists("logo_$1.png") ) {
- $templatename = "_$1";
- } else {
- $templatename = '';
- }
-
- my $filename = "logo$templatename.png";
-
- return { 'error' => '',
- 'logo' => $conf->config_binary($filename),
- 'content_type' => 'image/png', #should allow gif, jpg too
- };
-}
-
-
-sub list_invoices {
- my $p = shift;
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- my $custnum = $session->{'custnum'};
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
- or return { 'error' => "unknown custnum $custnum" };
-
- my @cust_bill = $cust_main->cust_bill;
-
- return { 'error' => '',
- 'invoices' => [ map { { 'invnum' => $_->invnum,
- '_date' => $_->_date,
- }
- } @cust_bill
- ]
- };
-}
-
-sub cancel {
- my $p = shift;
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- my $custnum = $session->{'custnum'};
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
- or return { 'error' => "unknown custnum $custnum" };
-
- my @errors = $cust_main->cancel( 'quiet'=>1 );
-
- my $error = scalar(@errors) ? join(' / ', @errors) : '';
-
- return { 'error' => $error };
-
-}
-
-sub list_pkgs {
- my $p = shift;
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- #return { 'cust_pkg' => [ map { $_->hashref } $cust_main->ncancelled_pkgs ] };
-
- my $conf = new FS::Conf;
-
- { 'svcnum' => $session->{'svcnum'},
- 'custnum' => $custnum,
- 'cust_pkg' => [ map {
- { $_->hash,
- $_->part_pkg->hash,
- part_svc =>
- [ map $_->hashref, $_->available_part_svc ],
- cust_svc =>
- [ map { my $ref = { $_->hash,
- label => [ $_->label ],
- };
- $ref->{_password} = $_->svc_x->_password
- if $context eq 'agent'
- && $conf->exists('agent-showpasswords')
- && $_->part_svc->svcdb eq 'svc_acct';
- $ref;
- } $_->cust_svc
- ],
- };
- } $cust_main->ncancelled_pkgs
- ],
- 'small_custview' =>
- small_custview( $cust_main, $conf->config('countrydefault') ),
- };
-
-}
-
-sub list_svcs {
- my $p = shift;
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- my @cust_svc = ();
- #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) {
- foreach my $cust_pkg ( $p->{'ncancelled'}
- ? $cust_main->ncancelled_pkgs
- : $cust_main->unsuspended_pkgs ) {
- push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context
- }
- @cust_svc = grep { $_->part_svc->svcdb eq $p->{'svcdb'} } @cust_svc
- if $p->{'svcdb'};
-
- #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username }
- # @svc_x;
-
- {
- #no#'svcnum' => $session->{'svcnum'},
- 'custnum' => $custnum,
- 'svcs' => [ map {
- my $svc_x = $_->svc_x;
- my($label, $value) = $_->label;
- my $part_pkg = $svc_x->cust_svc->cust_pkg->part_pkg;
-
- { 'svcnum' => $_->svcnum,
- 'label' => $label,
- 'value' => $value,
- 'username' => $svc_x->username,
- 'email' => $svc_x->email,
- 'seconds' => $svc_x->seconds,
- 'upbytes' => FS::UI::bytecount::display_bytecount($svc_x->upbytes),
- 'downbytes' => FS::UI::bytecount::display_bytecount($svc_x->downbytes),
- 'totalbytes'=> FS::UI::bytecount::display_bytecount($svc_x->totalbytes),
- 'recharge_amount' => $part_pkg->option('recharge_amount', 1),
- 'recharge_seconds' => $part_pkg->option('recharge_seconds', 1),
- 'recharge_upbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_upbytes', 1)),
- 'recharge_downbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_downbytes', 1)),
- 'recharge_totalbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_totalbytes', 1)),
- # more...
- };
- }
- @cust_svc
- ],
- };
-
-}
-
-sub _list_svc_usage {
- my($svc_acct, $begin, $end) = @_;
- my @usage = ();
- foreach my $part_export (
- map { qsearch ( 'part_export', { 'exporttype' => $_ } ) }
- qw (sqlradius sqlradius_withdomain')
- ) {
-
- push @usage, @ { $part_export->usage_sessions($begin, $end, $svc_acct) };
- }
- (@usage);
-}
-
-sub list_svc_usage {
- _usage_details(\&_list_svc_usage, @_);
-}
-
-sub _list_support_usage {
- my($svc_acct, $begin, $end) = @_;
- my @usage = ();
- foreach ( grep { $begin <= $_->_date && $_->_date <= $end }
- qsearch('acct_rt_transaction', { 'svcnum' => $svc_acct->svcnum })
- ) {
- push @usage, { 'seconds' => $_->seconds,
- 'support' => $_->support,
- '_date' => $_->_date,
- 'id' => $_->transaction_id,
- 'creator' => $_->creator,
- 'subject' => $_->subject,
- 'status' => $_->status,
- 'ticketid' => $_->ticketid,
- };
- }
- (@usage);
-}
-
-sub list_support_usage {
- _usage_details(\&_list_support_usage, @_);
-}
-
-sub _usage_details {
- my ($callback, $p) = (shift,shift);
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'svcnum' => $p->{'svcnum'} };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $svc_acct = qsearchs ( 'svc_acct', $search );
- return { 'error' => 'No service selected in list_svc_usage' }
- unless $svc_acct;
-
- my $freq = $svc_acct->cust_svc->cust_pkg->part_pkg->freq;
- my $start = $svc_acct->cust_svc->cust_pkg->setup;
- #my $end = $svc_acct->cust_svc->cust_pkg->bill; # or time?
- my $end = time;
-
- unless($p->{beginning}){
- $p->{beginning} = $svc_acct->cust_svc->cust_pkg->last_bill;
- $p->{ending} = $end;
- }
-
- my (@usage) = &$callback($svc_acct,$p->{beginning},$p->{ending});
-
- #kinda false laziness with FS::cust_main::bill, but perhaps
- #we should really change this bit to DateTime and DateTime::Duration
- #
- #change this bit to use Date::Manip? CAREFUL with timezones (see
- # mailing list archive)
- my ($nsec,$nmin,$nhour,$nmday,$nmon,$nyear) =
- (localtime($p->{ending}) )[0,1,2,3,4,5];
- my ($psec,$pmin,$phour,$pmday,$pmon,$pyear) =
- (localtime($p->{beginning}) )[0,1,2,3,4,5];
-
- if ( $freq =~ /^\d+$/ ) {
- $nmon += $freq;
- until ( $nmon < 12 ) { $nmon -= 12; $nyear++; }
- $pmon -= $freq;
- until ( $pmon >= 0 ) { $pmon += 12; $pyear--; }
- } elsif ( $freq =~ /^(\d+)w$/ ) {
- my $weeks = $1;
- $nmday += $weeks * 7;
- $pmday -= $weeks * 7;
- } elsif ( $freq =~ /^(\d+)d$/ ) {
- my $days = $1;
- $nmday += $days;
- $pmday -= $days;
- } elsif ( $freq =~ /^(\d+)h$/ ) {
- my $hours = $1;
- $nhour += $hours;
- $phour -= $hours;
- } else {
- return { 'error' => "unparsable frequency: ". $freq };
- }
-
- my $previous = timelocal_nocheck($psec,$pmin,$phour,$pmday,$pmon,$pyear);
- my $next = timelocal_nocheck($nsec,$nmin,$nhour,$nmday,$nmon,$nyear);
-
- {
- 'error' => '',
- 'svcnum' => $p->{svcnum},
- 'beginning' => $p->{beginning},
- 'ending' => $p->{ending},
- 'previous' => ($previous > $start) ? $previous : $start,
- 'next' => ($next < $end) ? $next : $end,
- 'usage' => \@usage,
- };
-}
-
-sub order_pkg {
- my $p = shift;
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- my $status = $cust_main->status;
- #false laziness w/ClientAPI/Signup.pm
-
- my $cust_pkg = new FS::cust_pkg ( {
- 'custnum' => $custnum,
- 'pkgpart' => $p->{'pkgpart'},
- } );
- my $error = $cust_pkg->check;
- return { 'error' => $error } if $error;
-
- my @svc = ();
- unless ( $p->{'svcpart'} eq 'none' ) {
-
- my $svcdb;
- my $svcpart = '';
- if ( $p->{'svcpart'} =~ /^(\d+)$/ ) {
- $svcpart = $1;
- my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
- return { 'error' => "Unknown svcpart $svcpart" } unless $part_svc;
- $svcdb = $part_svc->svcdb;
- } else {
- $svcdb = 'svc_acct';
- }
- $svcpart ||= $cust_pkg->part_pkg->svcpart($svcdb);
-
- my %fields = (
- 'svc_acct' => [ qw( username domsvc _password sec_phrase popnum ) ],
- 'svc_domain' => [ qw( domain ) ],
- 'svc_external' => [ qw( id title ) ],
- );
-
- my $svc_x = "FS::$svcdb"->new( {
- 'svcpart' => $svcpart,
- map { $_ => $p->{$_} } @{$fields{$svcdb}}
- } );
-
- if ( $svcdb eq 'svc_acct' ) {
- my @acct_snarf;
- my $snarfnum = 1;
- while ( length($p->{"snarf_machine$snarfnum"}) ) {
- my $acct_snarf = new FS::acct_snarf ( {
- 'machine' => $p->{"snarf_machine$snarfnum"},
- 'protocol' => $p->{"snarf_protocol$snarfnum"},
- 'username' => $p->{"snarf_username$snarfnum"},
- '_password' => $p->{"snarf_password$snarfnum"},
- } );
- $snarfnum++;
- push @acct_snarf, $acct_snarf;
- }
- $svc_x->child_objects( \@acct_snarf );
- }
-
- my $y = $svc_x->setdefault; # arguably should be in new method
- return { 'error' => $y } if $y && !ref($y);
-
- $error = $svc_x->check;
- return { 'error' => $error } if $error;
-
- push @svc, $svc_x;
-
- }
-
- use Tie::RefHash;
- tie my %hash, 'Tie::RefHash';
- %hash = ( $cust_pkg => \@svc );
- #msgcat
- $error = $cust_main->order_pkgs( \%hash, '', 'noexport' => 1 );
- return { 'error' => $error } if $error;
-
- my $conf = new FS::Conf;
- if ( $conf->exists('signup_server-realtime') ) {
-
- my $bill_error = _do_bop_realtime( $cust_main, $status );
-
- if ($bill_error) {
- $cust_pkg->cancel('quiet'=>1);
- return $bill_error;
- } else {
- $cust_pkg->reexport;
- }
-
- } else {
- $cust_pkg->reexport;
- }
-
- return { error => '', pkgnum => $cust_pkg->pkgnum };
-
-}
-
-sub change_pkg {
- my $p = shift;
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- my $status = $cust_main->status;
- my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $p->{pkgnum} } )
- or return { 'error' => "unknown package $p->{pkgnum}" };
-
- my @newpkg;
- my $error = FS::cust_pkg::order( $custnum,
- [$p->{pkgpart}],
- [$p->{pkgnum}],
- \@newpkg,
- );
-
- my $conf = new FS::Conf;
- if ( $conf->exists('signup_server-realtime') ) {
-
- my $bill_error = _do_bop_realtime( $cust_main, $status );
-
- if ($bill_error) {
- $newpkg[0]->suspend;
- return $bill_error;
- } else {
- $newpkg[0]->reexport;
- }
-
- } else {
- $newpkg[0]->reexport;
- }
-
- return { error => '', pkgnum => $cust_pkg->pkgnum };
-
-}
-
-sub order_recharge {
- my $p = shift;
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- my $status = $cust_main->status;
- my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $p->{'svcnum'} } )
- or return { 'error' => "unknown service " . $p->{'svcnum'} };
-
- my $svc_x = $cust_svc->svc_x;
- my $part_pkg = $cust_svc->cust_pkg->part_pkg;
-
- my %vhash =
- map { $_ =~ /^recharge_(.*)$/; $1, $part_pkg->option($_, 1) }
- qw ( recharge_seconds recharge_upbytes recharge_downbytes
- recharge_totalbytes );
- my $amount = $part_pkg->option('recharge_amount', 1);
-
- my ($l, $v, $d) = $cust_svc->label; # blah
- my $pkg = "Recharge $v";
-
- my $bill_error = $cust_main->charge($amount, $pkg,
- "time: $vhash{seconds}, up: $vhash{upbytes}," .
- "down: $vhash{downbytes}, total: $vhash{totalbytes}",
- $part_pkg->taxclass); #meh
-
- my $conf = new FS::Conf;
- if ( $conf->exists('signup_server-realtime') && !$bill_error ) {
-
- $bill_error = _do_bop_realtime( $cust_main, $status );
-
- if ($bill_error) {
- return $bill_error;
- } else {
- my $error = $svc_x->recharge (\%vhash);
- return { 'error' => $error } if $error;
- }
-
- } else {
- my $error = $bill_error;
- $error ||= $svc_x->recharge (\%vhash);
- return { 'error' => $error } if $error;
- }
-
- return { error => '', svc => $cust_svc->part_svc->svc };
-
-}
-
-sub _do_bop_realtime {
- my ($cust_main, $status) = (shift, shift);
-
- my $old_balance = $cust_main->balance;
-
- my $bill_error = $cust_main->bill
- || $cust_main->apply_payments_and_credits
- || $cust_main->collect('realtime' => 1);
-
- if ( $cust_main->balance > $old_balance
- && $cust_main->balance > 0
- && ( $cust_main->payby !~ /^(BILL|DCRD|DCHK)$/ ?
- 1 : $status eq 'suspended' ) ) {
- #this makes sense. credit is "un-doing" the invoice
- my $conf = new FS::Conf;
- $cust_main->credit( sprintf("%.2f", $cust_main->balance - $old_balance ),
- 'self-service decline',
- 'reason_type' => $conf->config('signup_credit_type'),
- );
- $cust_main->apply_credits( 'order' => 'newest' );
-
- return { 'error' => '_decline', 'bill_error' => $bill_error };
- }
-
- '';
-}
-
-sub cancel_pkg {
- my $p = shift;
- my $session = _cache->get($p->{'session_id'})
- or return { 'error' => "Can't resume session" }; #better error message
-
- my $custnum = $session->{'custnum'};
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
- or return { 'error' => "unknown custnum $custnum" };
-
- my $pkgnum = $p->{'pkgnum'};
-
- my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
- 'pkgnum' => $pkgnum, } )
- or return { 'error' => "unknown pkgnum $pkgnum" };
-
- my $error = $cust_pkg->cancel( 'quiet'=>1 );
- return { 'error' => $error };
-
-}
-
-sub provision_acct {
- my $p = shift;
-
- return { 'error' => gettext('passwords_dont_match') }
- if $p->{'_password'} ne $p->{'_password2'};
- return { 'error' => gettext('empty_password') }
- unless length($p->{'_password'});
-
- if ($p->{'domsvc'}) {
- my %domains = domain_select_hash FS::svc_acct(map { $_ => $p->{$_} }
- qw ( svcpart pkgnum ) );
- return { 'error' => gettext('invalid_domain') }
- unless ($domains{$p->{'domsvc'}});
- }
-
- _provision( 'FS::svc_acct',
- [qw(username _password domsvc)],
- [qw(username _password domsvc)],
- $p,
- @_
- );
-}
-
-sub provision_external {
- my $p = shift;
- #_provision( 'FS::svc_external', [qw(id title)], [qw(id title)], $p, @_ );
- _provision( 'FS::svc_external',
- [],
- [qw(id title)],
- $p,
- @_
- );
-}
-
-sub _provision {
- my( $class, $fields, $return_fields, $p ) = splice(@_, 0, 4);
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- my $pkgnum = $p->{'pkgnum'};
-
- my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
- 'pkgnum' => $pkgnum,
- } )
- or return { 'error' => "unknown pkgnum $pkgnum" };
-
- my $part_svc = qsearchs('part_svc', { 'svcpart' => $p->{'svcpart'} } )
- or return { 'error' => "unknown svcpart $p->{'svcpart'}" };
-
- my $svc_x = $class->new( {
- 'pkgnum' => $p->{'pkgnum'},
- 'svcpart' => $p->{'svcpart'},
- map { $_ => $p->{$_} } @$fields
- } );
- my $error = $svc_x->insert;
- $svc_x = qsearchs($svc_x->table, { 'svcnum' => $svc_x->svcnum })
- unless $error;
-
- return { 'svc' => $part_svc->svc,
- 'error' => $error,
- map { $_ => $svc_x->get($_) } @$return_fields
- };
-
-}
-
-sub part_svc_info {
- my $p = shift;
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- my $pkgnum = $p->{'pkgnum'};
-
- my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
- 'pkgnum' => $pkgnum,
- } )
- or return { 'error' => "unknown pkgnum $pkgnum" };
-
- my $svcpart = $p->{'svcpart'};
-
- my $pkg_svc = qsearchs('pkg_svc', { 'pkgpart' => $cust_pkg->pkgpart,
- 'svcpart' => $svcpart, } )
- or return { 'error' => "unknown svcpart $svcpart for pkgnum $pkgnum" };
- my $part_svc = $pkg_svc->part_svc;
-
- my $conf = new FS::Conf;
-
- return {
- 'svc' => $part_svc->svc,
- 'svcdb' => $part_svc->svcdb,
- 'pkgnum' => $pkgnum,
- 'svcpart' => $svcpart,
- 'custnum' => $custnum,
-
- 'security_phrase' => 0, #XXX !
- 'svc_acct_pop' => [], #XXX !
- 'popnum' => '',
- 'init_popstate' => '',
- 'popac' => '',
- 'acstate' => '',
-
- 'small_custview' =>
- small_custview( $cust_main, $conf->config('countrydefault') ),
-
- };
-
-}
-
-sub unprovision_svc {
- my $p = shift;
-
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- my $search = { 'custnum' => $custnum };
- $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- my $cust_main = qsearchs('cust_main', $search )
- or return { 'error' => "unknown custnum $custnum" };
-
- my $svcnum = $p->{'svcnum'};
-
- my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svcnum, } )
- or return { 'error' => "unknown svcnum $svcnum" };
-
- return { 'error' => "Service $svcnum does not belong to customer $custnum" }
- unless $cust_svc->cust_pkg->custnum == $custnum;
-
- my $conf = new FS::Conf;
-
- return { 'svc' => $cust_svc->part_svc->svc,
- 'error' => $cust_svc->cancel,
- 'small_custview' =>
- small_custview( $cust_main, $conf->config('countrydefault') ),
- };
-
-}
-
-sub myaccount_passwd {
- my $p = shift;
- my($context, $session, $custnum) = _custoragent_session_custnum($p);
- return { 'error' => $session } if $context eq 'error';
-
- return { 'error' => "New passwords don't match." }
- if $p->{'new_password'} ne $p->{'new_password2'};
-
- return { 'error' => 'Enter new password' }
- unless length($p->{'new_password'});
-
- #my $search = { 'custnum' => $custnum };
- #$search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
- $custnum =~ /^(\d+)$/ or die "illegal custnum";
- my $search = " AND custnum = $1";
- $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent';
-
- my $svc_acct = qsearchs( {
- 'table' => 'svc_acct',
- 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '.
- 'LEFT JOIN cust_pkg USING ( pkgnum ) '.
- 'LEFT JOIN cust_main USING ( custnum ) ',
- 'hashref' => { 'svcnum' => $p->{'svcnum'}, },
- 'extra_sql' => $search, #important
- } )
- or return { 'error' => "Service not found" };
-
- $svc_acct->_password($p->{'new_password'});
- my $error = $svc_acct->replace();
-
- my($label, $value) = $svc_acct->cust_svc->label;
-
- return { 'error' => $error,
- 'label' => $label,
- 'value' => $value,
- };
-
-}
-
-#--
-
-sub _custoragent_session_custnum {
- my $p = shift;
-
- my($context, $session, $custnum);
- if ( $p->{'session_id'} ) {
-
- $context = 'customer';
- $session = _cache->get($p->{'session_id'})
- or return ( 'error' => "Can't resume session" ); #better error message
- $custnum = $session->{'custnum'};
-
- } elsif ( $p->{'agent_session_id'} ) {
-
- $context = 'agent';
- my $agent_cache = new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::Agent',
- } );
- $session = $agent_cache->get($p->{'agent_session_id'})
- or return ( 'error' => "Can't resume session" ); #better error message
- $custnum = $p->{'custnum'};
-
- } else {
- return ( 'error' => "Can't resume session" ); #better error message
- }
-
- ($context, $session, $custnum);
-
-}
-
-1;
-
diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm
deleted file mode 100644
index 61325b9..0000000
--- a/FS/FS/ClientAPI/Signup.pm
+++ /dev/null
@@ -1,514 +0,0 @@
-package FS::ClientAPI::Signup;
-
-use strict;
-use vars qw($DEBUG $me);
-use Data::Dumper;
-use Tie::RefHash;
-use FS::Conf;
-use FS::Record qw(qsearch qsearchs dbdef);
-use FS::Msgcat qw(gettext);
-use FS::Misc qw(card_types);
-use FS::ClientAPI_SessionCache;
-use FS::agent;
-use FS::cust_main_county;
-use FS::part_pkg;
-use FS::svc_acct_pop;
-use FS::cust_main;
-use FS::cust_pkg;
-use FS::svc_acct;
-use FS::acct_snarf;
-use FS::queue;
-use FS::reg_code;
-
-$DEBUG = 0;
-$me = '[FS::ClientAPI::Signup]';
-
-sub signup_info {
- my $packet = shift;
-
- warn "$me signup_info called on $packet\n" if $DEBUG;
-
- my $conf = new FS::Conf;
-
- my $cache = new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::Signup',
- } );
- my $signup_info_cache = $cache->get('signup_info_cache');
-
- if ( $signup_info_cache ) {
-
- warn "$me loading cached signup info\n" if $DEBUG > 1;
-
- } else {
-
- warn "$me populating signup info cache\n" if $DEBUG > 1;
-
- my $agentnum2part_pkg =
- {
- map {
- my $href = $_->pkgpart_hashref;
- $_->agentnum =>
- [
- map { { 'payby' => [ $_->payby ],
- 'freq_pretty' => $_->freq_pretty,
- 'options' => { $_->options },
- %{$_->hashref}
- } }
- grep { $_->svcpart('svc_acct') && $href->{ $_->pkgpart } }
- qsearch( 'part_pkg', { 'disabled' => '' } )
- ];
- } qsearch('agent', { 'disabled' => '' })
- };
-
- my $msgcat = { map { $_=>gettext($_) }
- qw( passwords_dont_match invalid_card unknown_card_type
- not_a empty_password illegal_or_empty_text )
- };
- warn "msgcat: ". Dumper($msgcat). "\n" if $DEBUG > 2;
-
- my $label = { map { $_ => FS::Msgcat::_gettext($_) }
- qw( stateid stateid_state )
- };
- warn "label: ". Dumper($label). "\n" if $DEBUG > 2;
-
- $signup_info_cache = {
- 'cust_main_county' => [ map $_->hashref,
- qsearch('cust_main_county', {} )
- ],
-
- 'agent' => [ map $_->hashref,
- qsearch('agent', { 'disabled' => '' } )
- ],
-
- 'part_referral' => [ map $_->hashref,
- qsearch('part_referral', { 'disabled' => '' } )
- ],
-
- 'agentnum2part_pkg' => $agentnum2part_pkg,
-
- 'svc_acct_pop' => [ map $_->hashref, qsearch('svc_acct_pop',{} ) ],
-
- 'emailinvoiceonly' => $conf->exists('emailinvoiceonly'),
-
- 'security_phrase' => $conf->exists('security_phrase'),
-
- 'payby' => [ $conf->config('signup_server-payby') ],
-
- 'card_types' => card_types(),
-
- 'paytypes' => [ @FS::cust_main::paytypes ],
-
- 'cvv_enabled' => 1,
-
- 'stateid_enabled' => $conf->exists('show_stateid'),
-
- 'paystate_enabled' => $conf->exists('show_bankstate'),
-
- 'ship_enabled' => 1,
-
- 'msgcat' => $msgcat,
-
- 'label' => $label,
-
- 'statedefault' => scalar($conf->config('statedefault')) || 'CA',
-
- 'countrydefault' => scalar($conf->config('countrydefault')) || 'US',
-
- 'refnum' => scalar($conf->config('signup_server-default_refnum')),
-
- 'default_pkgpart' => scalar($conf->config('signup_server-default_pkgpart')),
-
- };
-
- $cache->set('signup_info_cache', $signup_info_cache);
-
- }
-
- my $signup_info = { %$signup_info_cache };
- warn "$me signup info loaded\n" if $DEBUG > 1;
- warn Dumper($signup_info). "\n" if $DEBUG > 2;
-
- my @addl = qw( signup_server-classnum2 signup_server-classnum3 );
-
- if ( grep { $conf->exists($_) } @addl ) {
-
- $signup_info->{optional_packages} = [];
-
- foreach my $addl ( @addl ) {
-
- warn "$me adding optional package info\n" if $DEBUG > 1;
-
- my $classnum = $conf->config($addl) or next;
-
- my @pkgs = map { {
- 'freq_pretty' => $_->freq_pretty,
- 'options' => { $_->options },
- %{ $_->hashref }
- };
- }
- qsearch( 'part_pkg', { classnum => $classnum } );
-
- push @{$signup_info->{optional_packages}}, \@pkgs;
-
- warn "$me done adding opt. package info for $classnum\n" if $DEBUG > 1;
-
- }
-
- }
-
- my $agentnum = $packet->{'agentnum'}
- || $conf->config('signup_server-default_agentnum');
- $agentnum =~ /^(\d*)$/ or die "illegal agentnum";
- $agentnum = $1;
-
- my $session = '';
- if ( exists $packet->{'session_id'} ) {
-
- warn "$me loading agent session\n" if $DEBUG > 1;
- my $cache = new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::Agent',
- } );
- $session = $cache->get($packet->{'session_id'});
- if ( $session ) {
- $agentnum = $session->{'agentnum'};
- } else {
- return { 'error' => "Can't resume session" }; #better error message
- }
- warn "$me done loading agent session\n" if $DEBUG > 1;
-
- } elsif ( exists $packet->{'customer_session_id'} ) {
-
- warn "$me loading customer session\n" if $DEBUG > 1;
- my $cache = new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::MyAccount',
- } );
- $session = $cache->get($packet->{'customer_session_id'});
- if ( $session ) {
- my $custnum = $session->{'custnum'};
- my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum });
- return { 'error' => "Can't find your customer record" } unless $cust_main;
- $agentnum = $cust_main->agentnum;
- } else {
- return { 'error' => "Can't resume session" }; #better error message
- }
- warn "$me done loading customer session\n" if $DEBUG > 1;
-
- }
-
- $signup_info->{'part_pkg'} = [];
-
- if ( $packet->{'reg_code'} ) {
-
- warn "$me setting package list via reg_code\n" if $DEBUG > 1;
-
- $signup_info->{'part_pkg'} =
- [ map { { 'payby' => [ $_->payby ],
- 'freq_pretty' => $_->freq_pretty,
- 'options' => { $_->options },
- %{$_->hashref}
- };
- }
- grep { $_->svcpart('svc_acct') }
- map { $_->part_pkg }
- qsearchs( 'reg_code', { 'code' => $packet->{'reg_code'},
- 'agentnum' => $agentnum, } )
-
- ];
-
- $signup_info->{'error'} = 'Unknown registration code'
- unless @{ $signup_info->{'part_pkg'} };
-
- warn "$me done setting package list via reg_code\n" if $DEBUG > 1;
-
- } elsif ( $packet->{'promo_code'} ) {
-
- warn "$me setting package list via promo_code\n" if $DEBUG > 1;
-
- $signup_info->{'part_pkg'} =
- [ map { { 'payby' => [ $_->payby ],
- 'freq_pretty' => $_->freq_pretty,
- 'options' => { $_->options },
- %{$_->hashref}
- } }
- grep { $_->svcpart('svc_acct') }
- qsearch( 'part_pkg', { 'promo_code' => {
- op=>'ILIKE',
- value=>$packet->{'promo_code'}
- },
- 'disabled' => '', } )
- ];
-
- $signup_info->{'error'} = 'Unknown promotional code'
- unless @{ $signup_info->{'part_pkg'} };
-
- warn "$me done setting package list via promo_code\n" if $DEBUG > 1;
- }
-
- if ( $agentnum ) {
-
- warn "$me setting agent-specific package list\n" if $DEBUG > 1;
- $signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum}
- unless @{ $signup_info->{'part_pkg'} };
- warn "$me done setting agent-specific package list\n" if $DEBUG > 1;
-
- warn "$me setting agent-specific adv. source list\n" if $DEBUG > 1;
- $signup_info->{'part_referral'} =
- [
- map { $_->hashref }
- qsearch( {
- 'table' => 'part_referral',
- 'hashref' => { 'disabled' => '' },
- 'extra_sql' => "AND ( agentnum = $agentnum ".
- " OR agentnum IS NULL ) ",
- },
- )
- ];
- warn "$me done setting agent-specific adv. source list\n" if $DEBUG > 1;
-
- }
- # else {
- # delete $signup_info->{'part_pkg'};
- #}
-
- warn "$me sorting package list\n" if $DEBUG > 1;
- $signup_info->{'part_pkg'} = [ sort { $a->{pkg} cmp $b->{pkg} } # case?
- @{ $signup_info->{'part_pkg'} }
- ];
- warn "$me done sorting package list\n" if $DEBUG > 1;
-
- if ( exists $packet->{'session_id'} ) {
- my $agent_signup_info = { %$signup_info };
- delete $agent_signup_info->{agentnum2part_pkg};
- $agent_signup_info->{'agent'} = $session->{'agent'};
- $agent_signup_info;
- } else {
- $signup_info;
- }
-
-}
-
-sub domain_select_hash {
- my $packet = shift;
-
- my $response = {};
-
- if ($packet->{pkgpart}) {
- my $part_pkg = qsearchs('part_pkg' => { 'pkgpart' => $packet->{pkgpart} } );
- #$packet->{svcpart} = $part_pkg->svcpart('svc_acct')
- $packet->{svcpart} = $part_pkg->svcpart
- if $part_pkg;
- }
-
- if ($packet->{svcpart}) {
- my $part_svc = qsearchs('part_svc' => { 'svcpart' => $packet->{svcpart} } );
- $response->{'domsvc'} = $part_svc->part_svc_column('domsvc')->columnvalue
- if ($part_svc && $part_svc->part_svc_column('domsvc')->columnflag eq 'D');
- }
-
- $response->{'domains'}
- = { domain_select_hash FS::svc_acct( map { $_ => $packet->{$_} }
- qw(svcpart pkgnum)
- ) };
-
- $response;
-}
-
-sub new_customer {
- my $packet = shift;
-
- my $conf = new FS::Conf;
-
- #things that aren't necessary in base class, but are for signup server
- #return "Passwords don't match"
- # if $hashref->{'_password'} ne $hashref->{'_password2'}
- return { 'error' => gettext('empty_password') }
- unless length($packet->{'_password'});
- # a bit inefficient for large numbers of pops
- return { 'error' => gettext('no_access_number_selected') }
- unless $packet->{'popnum'} || !scalar(qsearch('svc_acct_pop',{} ));
-
- my $agentnum;
- if ( exists $packet->{'session_id'} ) {
- my $cache = new FS::ClientAPI_SessionCache( {
- 'namespace' => 'FS::ClientAPI::Agent',
- } );
- my $session = $cache->get($packet->{'session_id'});
- if ( $session ) {
- $agentnum = $session->{'agentnum'};
- } else {
- return { 'error' => "Can't resume session" }; #better error message
- }
- } else {
- $agentnum = $packet->{agentnum}
- || $conf->config('signup_server-default_agentnum');
- }
-
- #shares some stuff with htdocs/edit/process/cust_main.cgi... take any
- # common that are still here and library them.
- my $cust_main = new FS::cust_main ( {
- #'custnum' => '',
- 'agentnum' => $agentnum,
- 'refnum' => $packet->{refnum}
- || $conf->config('signup_server-default_refnum'),
-
- map { $_ => $packet->{$_} } qw(
-
- last first ss company address1 address2
- city county state zip country
- daytime night fax stateid stateid_state
-
- ship_last ship_first ship_ss ship_company ship_address1 ship_address2
- ship_city ship_county ship_state ship_zip ship_country
- ship_daytime ship_night ship_fax
-
- payby
- payinfo paycvv paydate payname paystate paytype
- paystart_month paystart_year payissue
- payip
-
- referral_custnum comments
- )
-
- } );
-
- return { 'error' => "Illegal payment type" }
- unless grep { $_ eq $packet->{'payby'} }
- $conf->config('signup_server-payby');
-
- $cust_main->payinfo($cust_main->daytime)
- if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
-
- my @invoicing_list = $packet->{'invoicing_list'}
- ? split( /\s*\,\s*/, $packet->{'invoicing_list'} )
- : ();
-
- $packet->{'pkgpart'} =~ /^(\d+)$/ or '' =~ /^()$/;
- my $pkgpart = $1;
- return { 'error' => 'Please select a package' } unless $pkgpart; #msgcat
-
- my $part_pkg =
- qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
- or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" };
- my $svcpart = $part_pkg->svcpart('svc_acct');
-
- my $reg_code = '';
- if ( $packet->{'reg_code'} ) {
- $reg_code = qsearchs( 'reg_code', { 'code' => $packet->{'reg_code'},
- 'agentnum' => $agentnum, } )
- or return { 'error' => 'Unknown registration code' };
- }
-
- my $cust_pkg = new FS::cust_pkg ( {
- #later#'custnum' => $custnum,
- 'pkgpart' => $packet->{'pkgpart'},
- 'promo_code' => $packet->{'promo_code'},
- 'reg_code' => $packet->{'reg_code'},
- } );
- #my $error = $cust_pkg->check;
- #return { 'error' => $error } if $error;
-
- my $svc_acct = new FS::svc_acct ( {
- 'svcpart' => $svcpart,
- map { $_ => $packet->{$_} }
- qw( username _password sec_phrase popnum ),
- } );
-
- my @acct_snarf;
- my $snarfnum = 1;
- while ( exists($packet->{"snarf_machine$snarfnum"})
- && length($packet->{"snarf_machine$snarfnum"}) ) {
- my $acct_snarf = new FS::acct_snarf ( {
- 'machine' => $packet->{"snarf_machine$snarfnum"},
- 'protocol' => $packet->{"snarf_protocol$snarfnum"},
- 'username' => $packet->{"snarf_username$snarfnum"},
- '_password' => $packet->{"snarf_password$snarfnum"},
- } );
- $snarfnum++;
- push @acct_snarf, $acct_snarf;
- }
- $svc_acct->child_objects( \@acct_snarf );
-
- my $y = $svc_acct->setdefault; # arguably should be in new method
- return { 'error' => $y } if $y && !ref($y);
-
- #$error = $svc_acct->check;
- #return { 'error' => $error } if $error;
-
- #setup a job dependancy to delay provisioning
- my $placeholder = new FS::queue ( {
- 'job' => 'FS::ClientAPI::Signup::__placeholder',
- 'status' => 'locked',
- } );
- my $error = $placeholder->insert;
- return { 'error' => $error } if $error;
-
- use Tie::RefHash;
- tie my %hash, 'Tie::RefHash';
- %hash = ( $cust_pkg => [ $svc_acct ] );
- #msgcat
- $error = $cust_main->insert(
- \%hash,
- \@invoicing_list,
- 'depend_jobnum' => $placeholder->jobnum,
- );
- if ( $error ) {
- my $perror = $placeholder->delete;
- $error .= " (Additionally, error removing placeholder: $perror)" if $perror;
- return { 'error' => $error };
- }
-
- if ( $conf->exists('signup_server-realtime') ) {
-
- #warn "[fs_signup_server] Billing customer...\n" if $Debug;
-
- my $bill_error = $cust_main->bill;
- #warn "[fs_signup_server] error billing new customer: $bill_error"
- # if $bill_error;
-
- $bill_error = $cust_main->apply_payments_and_credits;
- #warn "[fs_signup_server] error applying payments and credits for".
- # " new customer: $bill_error"
- # if $bill_error;
-
- $bill_error = $cust_main->collect('realtime' => 1);
- #warn "[fs_signup_server] error collecting from new customer: $bill_error"
- # if $bill_error;
-
- if ( $cust_main->balance > 0 ) {
-
- #this makes sense. credit is "un-doing" the invoice
- $cust_main->credit( $cust_main->balance, 'signup server decline',
- 'reason_type' => $conf->config('signup_credit_type'),
- );
- $cust_main->apply_credits;
-
- #should check list for errors...
- #$cust_main->suspend;
- local $FS::svc_Common::noexport_hack = 1;
- $cust_main->cancel('quiet'=>1);
-
- my $perror = $placeholder->depended_delete;
- warn "error removing provisioning jobs after decline: $perror" if $perror;
- unless ( $perror ) {
- $perror = $placeholder->delete;
- warn "error removing placeholder after decline: $perror" if $perror;
- }
-
- return { 'error' => '_decline' };
- }
-
- }
-
- if ( $reg_code ) {
- $error = $reg_code->delete;
- return { 'error' => $error } if $error;
- }
-
- $error = $placeholder->delete;
- return { 'error' => $error } if $error;
-
- return { error => '' };
-
-}
-
-1;
diff --git a/FS/FS/ClientAPI/passwd.pm b/FS/FS/ClientAPI/passwd.pm
deleted file mode 100644
index b22d761..0000000
--- a/FS/FS/ClientAPI/passwd.pm
+++ /dev/null
@@ -1,46 +0,0 @@
-package FS::ClientAPI::passwd;
-
-use strict;
-use FS::Record qw(qsearchs);
-use FS::svc_acct;
-use FS::svc_domain;
-
-sub passwd {
- my $packet = shift;
-
- my $domain = $FS::ClientAPI::domain || $packet->{'domain'};
- my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
- or return { error => "Domain $domain not found" };
-
- my $old_password = $packet->{'old_password'};
- my $new_password = $packet->{'new_password'};
- my $new_gecos = $packet->{'new_gecos'};
- my $new_shell = $packet->{'new_shell'};
-
- #false laziness w/FS::ClientAPI::MyAccount::login
-
- my $svc_acct = qsearchs( 'svc_acct', { 'username' => $packet->{'username'},
- 'domsvc' => $svc_domain->svcnum, }
- );
- return { error => 'User not found.' } unless $svc_acct;
- return { error => 'Incorrect password.' }
- unless $svc_acct->check_password($old_password);
-
- my %hash = $svc_acct->hash;
- my $new_svc_acct = new FS::svc_acct ( \%hash );
- $new_svc_acct->setfield('_password', $new_password )
- if $new_password && $new_password ne $old_password;
- $new_svc_acct->setfield('finger',$new_gecos) if $new_gecos;
- $new_svc_acct->setfield('shell',$new_shell) if $new_shell;
- my $error = $new_svc_acct->replace($svc_acct);
-
- return { error => $error };
-
-}
-
-sub chfn {}
-
-sub chsh {}
-
-1;
-
diff --git a/FS/FS/ClientAPI_SessionCache.pm b/FS/FS/ClientAPI_SessionCache.pm
deleted file mode 100644
index bfab805..0000000
--- a/FS/FS/ClientAPI_SessionCache.pm
+++ /dev/null
@@ -1,78 +0,0 @@
-package FS::ClientAPI_SessionCache;
-
-use strict;
-use vars qw($module);
-use FS::UID qw(datasrc);
-
-#ask FS::UID to run this stuff for us later
-install_callback FS::UID sub {
- my $conf = new FS::Conf;
- $module = $conf->config('selfservice_server-cache_module')
- || 'Cache::FileCache';
-};
-
-=head1 NAME
-
-FS::ClientAPI_SessionCache;
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-Minimal Cache::Cache-alike interface for storing session cache information.
-Backends to Cache::SharedMemoryCache, Cache::FileCache, or an internal
-implementation which stores information in the clientapi_session and
-clientapi_session_field database tables.
-
-=head1 METHODS
-
-=over 4
-
-=item new
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- unless ( $module =~ /^_Database$/ ) {
- eval "use $module;";
- die $@ if $@;
- my $self = $module->new(@_);
- $self->set_cache_root('%%%FREESIDE_CACHE%%%/clientapi_session.'.datasrc)
- if $module =~ /^Cache::FileCache$/;
- $self;
- } else {
- my $self = shift;
- bless ($self, $class);
- }
-}
-
-sub get {
- my($self, $session_id) = @_;
- die '_Database self-service session cache not yet implemented';
-}
-
-sub set {
- my($self, $session_id, $session, $expiration) = @_;
- die '_Database self-service session cache not yet implemented';
-}
-
-sub remove {
- my($self, $session_id) = @_;
- die '_Database self-service session cache not yet implemented';
-}
-
-=back
-
-=head1 BUGS
-
-Minimal documentation.
-
-=head1 SEE ALSO
-
-L<Cache::Cache>, L<FS::clientapi_session>, L<FS::clientapi_session_field>
-
-=cut
-
-1;
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
deleted file mode 100644
index b7edd0d..0000000
--- a/FS/FS/Conf.pm
+++ /dev/null
@@ -1,2207 +0,0 @@
-package FS::Conf;
-
-use vars qw($base_dir @config_items @base_items @card_types $DEBUG);
-use Carp;
-use IO::File;
-use File::Basename;
-use MIME::Base64;
-use FS::ConfItem;
-use FS::ConfDefaults;
-use FS::Conf_compat17;
-use FS::conf;
-use FS::Record qw(qsearch qsearchs);
-use FS::UID qw(dbh datasrc use_confcompat);
-
-$base_dir = '%%%FREESIDE_CONF%%%';
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::Conf - Freeside configuration values
-
-=head1 SYNOPSIS
-
- use FS::Conf;
-
- $conf = new FS::Conf;
-
- $value = $conf->config('key');
- @list = $conf->config('key');
- $bool = $conf->exists('key');
-
- $conf->touch('key');
- $conf->set('key' => 'value');
- $conf->delete('key');
-
- @config_items = $conf->config_items;
-
-=head1 DESCRIPTION
-
-Read and write Freeside configuration values. Keys currently map to filenames,
-but this may change in the future.
-
-=head1 METHODS
-
-=over 4
-
-=item new
-
-Create a new configuration object.
-
-=cut
-
-sub new {
- my($proto) = @_;
- my($class) = ref($proto) || $proto;
- my($self) = { 'base_dir' => $base_dir };
- bless ($self, $class);
-}
-
-=item base_dir
-
-Returns the base directory. By default this is /usr/local/etc/freeside.
-
-=cut
-
-sub base_dir {
- my($self) = @_;
- my $base_dir = $self->{base_dir};
- -e $base_dir or die "FATAL: $base_dir doesn't exist!";
- -d $base_dir or die "FATAL: $base_dir isn't a directory!";
- -r $base_dir or die "FATAL: Can't read $base_dir!";
- -x $base_dir or die "FATAL: $base_dir not searchable (executable)!";
- $base_dir =~ /^(.*)$/;
- $1;
-}
-
-=item config KEY [ AGENTNUM ]
-
-Returns the configuration value or values (depending on context) for key.
-The optional agent number selects an agent specific value instead of the
-global default if one is present.
-
-=cut
-
-sub _usecompat {
- my ($self, $method) = (shift, shift);
- carp "NO CONFIGURATION RECORDS FOUND -- USING COMPATIBILITY MODE"
- if use_confcompat;
- my $compat = new FS::Conf_compat17 ("$base_dir/conf." . datasrc);
- $compat->$method(@_);
-}
-
-sub _config {
- my($self,$name,$agentnum)=@_;
- my $hashref = { 'name' => $name };
- $hashref->{agentnum} = $agentnum;
- local $FS::Record::conf = undef; # XXX evil hack prevents recursion
- my $cv = FS::Record::qsearchs('conf', $hashref);
- if (!$cv && defined($agentnum)) {
- $hashref->{agentnum} = '';
- $cv = FS::Record::qsearchs('conf', $hashref);
- }
- return $cv;
-}
-
-sub config {
- my $self = shift;
- return $self->_usecompat('config', @_) if use_confcompat;
-
- my($name,$agentnum)=@_;
- my $cv = $self->_config($name, $agentnum) or return;
-
- if ( wantarray ) {
- my $v = $cv->value;
- chomp $v;
- (split "\n", $v, -1);
- } else {
- (split("\n", $cv->value))[0];
- }
-}
-
-=item config_binary KEY [ AGENTNUM ]
-
-Returns the exact scalar value for key.
-
-=cut
-
-sub config_binary {
- my $self = shift;
- return $self->_usecompat('config_binary', @_) if use_confcompat;
-
- my($name,$agentnum)=@_;
- my $cv = $self->_config($name, $agentnum) or return;
- decode_base64($cv->value);
-}
-
-=item exists KEY [ AGENTNUM ]
-
-Returns true if the specified key exists, even if the corresponding value
-is undefined.
-
-=cut
-
-sub exists {
- my $self = shift;
- return $self->_usecompat('exists', @_) if use_confcompat;
-
- my($name,$agentnum)=@_;
- defined($self->_config($name, $agentnum));
-}
-
-#=item config_orbase KEY SUFFIX
-#
-#Returns the configuration value or values (depending on context) for
-#KEY_SUFFIX, if it exists, otherwise for KEY
-#
-#=cut
-
-# outmoded as soon as we shift to agentnum based config values
-# well, mostly. still useful for e.g. late notices, etc. in that we want
-# these to fall back to standard values
-sub config_orbase {
- my $self = shift;
- return $self->_usecompat('config_orbase', @_) if use_confcompat;
-
- my( $name, $suffix ) = @_;
- if ( $self->exists("${name}_$suffix") ) {
- $self->config("${name}_$suffix");
- } else {
- $self->config($name);
- }
-}
-
-=item invoice_templatenames
-
-Returns all possible invoice template names.
-
-=cut
-
-sub invoice_templatenames {
- my( $self ) = @_;
-
- my %templatenames = ();
- foreach my $item ( $self->config_items ) {
- foreach my $base ( @base_items ) {
- my( $main, $ext) = split(/\./, $base);
- $ext = ".$ext" if $ext;
- if ( $item->key =~ /^${main}_(.+)$ext$/ ) {
- $templatenames{$1}++;
- }
- }
- }
-
- sort keys %templatenames;
-
-}
-
-=item touch KEY [ AGENT ];
-
-Creates the specified configuration key if it does not exist.
-
-=cut
-
-sub touch {
- my $self = shift;
- return $self->_usecompat('touch', @_) if use_confcompat;
-
- my($name, $agentnum) = @_;
- unless ( $self->exists($name, $agentnum) ) {
- $self->set($name, '', $agentnum);
- }
-}
-
-=item set KEY VALUE [ AGENTNUM ];
-
-Sets the specified configuration key to the given value.
-
-=cut
-
-sub set {
- my $self = shift;
- return $self->_usecompat('set', @_) if use_confcompat;
-
- my($name, $value, $agentnum) = @_;
- $value =~ /^(.*)$/s;
- $value = $1;
-
- warn "[FS::Conf] SET $name\n" if $DEBUG;
-
- my $old = FS::Record::qsearchs('conf', {name => $name, agentnum => $agentnum});
- my $new = new FS::conf { $old ? $old->hash
- : ('name' => $name, 'agentnum' => $agentnum)
- };
- $new->value($value);
-
- my $error;
- if ($old) {
- $error = $new->replace($old);
- } else {
- $error = $new->insert;
- }
-
- die "error setting configuration value: $error \n"
- if $error;
-
-}
-
-=item set_binary KEY VALUE [ AGENTNUM ]
-
-Sets the specified configuration key to an exact scalar value which
-can be retrieved with config_binary.
-
-=cut
-
-sub set_binary {
- my $self = shift;
- return if use_confcompat;
-
- my($name, $value, $agentnum)=@_;
- $self->set($name, encode_base64($value), $agentnum);
-}
-
-=item delete KEY [ AGENTNUM ];
-
-Deletes the specified configuration key.
-
-=cut
-
-sub delete {
- my $self = shift;
- return $self->_usecompat('delete', @_) if use_confcompat;
-
- my($name, $agentnum) = @_;
- if ( my $cv = FS::Record::qsearchs('conf', {name => $name, agentnum => $agentnum}) ) {
- warn "[FS::Conf] DELETE $name\n";
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $cv->delete;
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- die "error setting configuration value: $error \n"
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- }
-}
-
-=item import_config_item CONFITEM DIR
-
- Imports the item specified by the CONFITEM (see L<FS::ConfItem>) into
-the database as a conf record (see L<FS::conf>). Imports from the file
-in the directory DIR.
-
-=cut
-
-sub import_config_item {
- my ($self,$item,$dir) = @_;
- my $key = $item->key;
- if ( -e "$dir/$key" && ! use_confcompat ) {
- warn "Inserting $key\n" if $DEBUG;
- local $/;
- my $value = readline(new IO::File "$dir/$key");
- if ($item->type eq 'binary') {
- $self->set_binary($key, $value);
- }else{
- $self->set($key, $value);
- }
- }else {
- warn "Not inserting $key\n" if $DEBUG;
- }
-}
-
-=item verify_config_item CONFITEM DIR
-
- Compares the item specified by the CONFITEM (see L<FS::ConfItem>) in
-the database to the legacy file value in DIR.
-
-=cut
-
-sub verify_config_item {
- return '' if use_confcompat;
- my ($self,$item,$dir) = @_;
- my $key = $item->key;
- my $type = $item->type;
-
- my $compat = new FS::Conf_compat17 $dir;
- my $error = '';
-
- $error .= "$key fails existential comparison; "
- if $self->exists($key) xor $compat->exists($key);
-
- unless ($type eq 'binary') {
- {
- no warnings;
- $error .= "$key fails scalar comparison; "
- unless scalar($self->config($key)) eq scalar($compat->config($key));
- }
-
- my (@new) = $self->config($key);
- my (@old) = $compat->config($key);
- unless ( scalar(@new) == scalar(@old)) {
- $error .= "$key fails list comparison; ";
- }else{
- my $r=1;
- foreach (@old) { $r=0 if ($_ cmp shift(@new)); }
- $error .= "$key fails list comparison; "
- unless $r;
- }
- }
-
- if ($type eq 'binary') {
- $error .= "$key fails binary comparison; "
- unless scalar($self->config_binary($key)) eq scalar($compat->config_binary($key));
- }
-
- if ($error =~ /existential comparison/ && $item->section eq 'deprecated') {
- my $proto;
- for ( @config_items ) { $proto = $_; last if $proto->key eq $key; }
- unless ($proto->key eq $key) {
- warn "removed config item $error\n" if $DEBUG;
- $error = '';
- }
- }
-
- $error;
-}
-
-#item _orbase_items OPTIONS
-#
-#Returns all of the possible extensible config items as FS::ConfItem objects.
-#See #L<FS::ConfItem>. OPTIONS consists of name value pairs. Possible
-#options include
-#
-# dir - the directory to search for configuration option files instead
-# of using the conf records in the database
-#
-#cut
-
-#quelle kludge
-sub _orbase_items {
- my ($self, %opt) = @_;
-
- my $listmaker = sub { my $v = shift;
- $v =~ s/_/!_/g;
- if ( $v =~ /\.(png|eps)$/ ) {
- $v =~ s/\./!_%./;
- }else{
- $v .= '!_%';
- }
- map { $_->name }
- FS::Record::qsearch( 'conf',
- {},
- '',
- "WHERE name LIKE '$v' ESCAPE '!'"
- );
- };
-
- if (exists($opt{dir}) && $opt{dir}) {
- $listmaker = sub { my $v = shift;
- if ( $v =~ /\.(png|eps)$/ ) {
- $v =~ s/\./_*./;
- }else{
- $v .= '_*';
- }
- map { basename $_ } glob($opt{dir}. "/$v" );
- };
- }
-
- ( map {
- my $proto;
- my $base = $_;
- for ( @config_items ) { $proto = $_; last if $proto->key eq $base; }
- die "don't know about $base items" unless $proto->key eq $base;
-
- map { new FS::ConfItem {
- 'key' => $_,
- 'section' => $proto->section,
- 'description' => 'Alternate ' . $proto->description . ' See the <a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Invoice_templates">billing documentation</a> for details.',
- 'type' => $proto->type,
- };
- } &$listmaker($base);
- } @base_items,
- );
-}
-
-=item config_items
-
-Returns all of the possible global/default configuration items as
-FS::ConfItem objects. See L<FS::ConfItem>.
-
-=cut
-
-sub config_items {
- my $self = shift;
- return $self->_usecompat('config_items', @_) if use_confcompat;
-
- ( @config_items, $self->_orbase_items(@_) );
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item init-config DIR
-
-Imports the non-deprecated configuration items from DIR (1.7 compatible)
-to conf records in the database.
-
-=cut
-
-sub init_config {
- my $dir = shift;
-
- {
- local $FS::UID::use_confcompat = 0;
- my $conf = new FS::Conf;
- foreach my $item ( $conf->config_items(dir => $dir) ) {
- $conf->import_config_item($item, $dir);
- my $error = $conf->verify_config_item($item, $dir);
- return $error if $error;
- }
-
- my $compat = new FS::Conf_compat17 $dir;
- foreach my $item ( $compat->config_items ) {
- my $error = $conf->verify_config_item($item, $dir);
- return $error if $error;
- }
- }
-
- $FS::UID::use_confcompat = 0;
- ''; #success
-}
-
-=back
-
-=head1 BUGS
-
-If this was more than just crud that will never be useful outside Freeside I'd
-worry that config_items is freeside-specific and icky.
-
-=head1 SEE ALSO
-
-"Configuration" in the web interface (config/config.cgi).
-
-=cut
-
-#Business::CreditCard
-@card_types = (
- "VISA card",
- "MasterCard",
- "Discover card",
- "American Express card",
- "Diner's Club/Carte Blanche",
- "enRoute",
- "JCB",
- "BankCard",
- "Switch",
- "Solo",
-);
-
-@base_items = qw (
- invoice_template
- invoice_latex
- invoice_latexreturnaddress
- invoice_latexfooter
- invoice_latexsmallfooter
- invoice_latexnotes
- invoice_html
- invoice_htmlreturnaddress
- invoice_htmlfooter
- invoice_htmlnotes
- logo.png
- logo.eps
- );
-
-@base_items = qw (
- invoice_template
- invoice_latex
- invoice_latexreturnaddress
- invoice_latexfooter
- invoice_latexsmallfooter
- invoice_latexnotes
- invoice_html
- invoice_htmlreturnaddress
- invoice_htmlfooter
- invoice_htmlnotes
- logo.png
- logo.eps
- );
-
-@config_items = map { new FS::ConfItem $_ } (
-
- {
- 'key' => 'address',
- 'section' => 'deprecated',
- 'description' => 'This configuration option is no longer used. See <a href="#invoice_template">invoice_template</a> instead.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'alerter_template',
- 'section' => 'billing',
- 'description' => 'Template file for billing method expiration alerts. See the <a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Credit_cards_and_Electronic_checks">billing documentation</a> for details.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'apacheip',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add an <i>apache</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be the current IP address to assign to new virtual hosts',
- 'type' => 'text',
- },
-
- {
- 'key' => 'encryption',
- 'section' => 'billing',
- 'description' => 'Enable encryption of credit cards.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'encryptionmodule',
- 'section' => 'billing',
- 'description' => 'Use which module for encryption?',
- 'type' => 'text',
- },
-
- {
- 'key' => 'encryptionpublickey',
- 'section' => 'billing',
- 'description' => 'Your RSA Public Key - Required if Encryption is turned on.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'encryptionprivatekey',
- 'section' => 'billing',
- 'description' => 'Your RSA Private Key - Including this will enable the "Bill Now" feature. However if the system is compromised, a hacker can use this key to decode the stored credit card information. This is generally not a good idea.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'business-onlinepayment',
- 'section' => 'billing',
- 'description' => '<a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support, at least three lines: processor, login, and password. An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\'). Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'business-onlinepayment-ach',
- 'section' => 'billing',
- 'description' => 'Alternate <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support for ACH transactions (defaults to regular <b>business-onlinepayment</b>). At least three lines: processor, login, and password. An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\'). Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'business-onlinepayment-description',
- 'section' => 'billing',
- 'description' => 'String passed as the description field to <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a>. Evaluated as a double-quoted perl string, with the following variables available: <code>$agent</code> (the agent name), and <code>$pkgs</code> (a comma-separated list of packages for which these charges apply)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'business-onlinepayment-email-override',
- 'section' => 'billing',
- 'description' => 'Email address used instead of customer email address when submitting a BOP transaction.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'business-onlinepayment-email_customer',
- 'section' => 'billing',
- 'description' => 'Controls the "email_customer" flag used by some Business::OnlinePayment processors to enable customer receipts.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'countrydefault',
- 'section' => 'UI',
- 'description' => 'Default two-letter country code (if not supplied, the default is `US\')',
- 'type' => 'text',
- },
-
- {
- 'key' => 'date_format',
- 'section' => 'UI',
- 'description' => 'Format for displaying dates',
- 'type' => 'select',
- 'select_hash' => [
- '%m/%d/%Y' => 'MM/DD/YYYY',
- '%Y/%m/%d' => 'YYYY/MM/DD',
- ],
- },
-
- {
- 'key' => 'deletecustomers',
- 'section' => 'UI',
- 'description' => 'Enable customer deletions. Be very careful! Deleting a customer will remove all traces that this customer ever existed! It should probably only be used when auditing a legacy database. Normally, you cancel all of a customers\' packages if they cancel service.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'deletepayments',
- 'section' => 'billing',
- 'description' => 'Enable deletion of unclosed payments. Really, with voids this is pretty much not recommended in any situation anymore. Be very careful! Only delete payments that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.',
- 'type' => [qw( checkbox text )],
- },
-
- {
- 'key' => 'deletecredits',
- 'section' => 'deprecated',
- 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.',
- 'type' => [qw( checkbox text )],
- },
-
- {
- 'key' => 'deleterefunds',
- 'section' => 'billing',
- 'description' => 'Enable deletion of unclosed refunds. Be very careful! Only delete refunds that were data-entry errors, not adjustments.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'dirhash',
- 'section' => 'shell',
- 'description' => 'Optional numeric value to control directory hashing. If positive, hashes directories for the specified number of levels from the front of the username. If negative, hashes directories for the specified number of levels from the end of the username. Some examples: <ul><li>1: user -> <a href="#home">/home</a>/u/user<li>2: user -> <a href="#home">/home</a>/u/s/user<li>-1: user -> <a href="#home">/home</a>/r/user<li>-2: user -> <a href="#home">home</a>/r/e/user</ul>',
- 'type' => 'text',
- },
-
- {
- 'key' => 'disable_customer_referrals',
- 'section' => 'UI',
- 'description' => 'Disable new customer-to-customer referrals in the web interface',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'editreferrals',
- 'section' => 'UI',
- 'description' => 'Enable advertising source modification for existing customers',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'emailinvoiceonly',
- 'section' => 'billing',
- 'description' => 'Disables postal mail invoices',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'disablepostalinvoicedefault',
- 'section' => 'billing',
- 'description' => 'Disables postal mail invoices as the default option in the UI. Be careful not to setup customers which are not sent invoices. See <a href ="#emailinvoiceauto">emailinvoiceauto</a>.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'emailinvoiceauto',
- 'section' => 'billing',
- 'description' => 'Automatically adds new accounts to the email invoice list',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'emailinvoiceautoalways',
- 'section' => 'billing',
- 'description' => 'Automatically adds new accounts to the email invoice list even when the list contains email addresses',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'exclude_ip_addr',
- 'section' => '',
- 'description' => 'Exclude these from the list of available broadband service IP addresses. (One per line)',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'hidecancelledpackages',
- 'section' => 'UI',
- 'description' => 'Prevent cancelled packages from showing up in listings (though they will still be in the database)',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'hidecancelledcustomers',
- 'section' => 'UI',
- 'description' => 'Prevent customers with only cancelled packages from showing up in listings (though they will still be in the database)',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'home',
- 'section' => 'shell',
- 'description' => 'For new users, prefixed to username to create a directory name. Should have a leading but not a trailing slash.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'invoice_from',
- 'section' => 'required',
- 'description' => 'Return address on email invoices',
- 'type' => 'text',
- },
-
- {
- 'key' => 'invoice_template',
- 'section' => 'billing',
- 'description' => 'Text template file for invoices. Used if no invoice_html template is defined, and also seen by users using non-HTML capable mail clients. See the <a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Plaintext_invoice_templates">billing documentation</a> for details.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_html',
- 'section' => 'billing',
- 'description' => 'Optional HTML template for invoices. See the <a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#HTML_invoice_templates">billing documentation</a> for details.',
-
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_htmlnotes',
- 'section' => 'billing',
- 'description' => 'Notes section for HTML invoices. Defaults to the same data in invoice_latexnotes if not specified.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_htmlfooter',
- 'section' => 'billing',
- 'description' => 'Footer for HTML invoices. Defaults to the same data in invoice_latexfooter if not specified.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_htmlreturnaddress',
- 'section' => 'billing',
- 'description' => 'Return address for HTML invoices. Defaults to the same data in invoice_latexreturnaddress if not specified.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latex',
- 'section' => 'billing',
- 'description' => 'Optional LaTeX template for typeset PostScript invoices. See the <a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Typeset_.28LaTeX.29_invoice_templates">billing documentation</a> for details.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latexnotes',
- 'section' => 'billing',
- 'description' => 'Notes section for LaTeX typeset PostScript invoices.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latexfooter',
- 'section' => 'billing',
- 'description' => 'Footer for LaTeX typeset PostScript invoices.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latexreturnaddress',
- 'section' => 'billing',
- 'description' => 'Return address for LaTeX typeset PostScript invoices.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latexsmallfooter',
- 'section' => 'billing',
- 'description' => 'Optional small footer for multi-page LaTeX typeset PostScript invoices.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_email_pdf',
- 'section' => 'billing',
- 'description' => 'Send PDF invoice as an attachment to emailed invoices. By default, includes the plain text invoice as the email body, unless invoice_email_pdf_note is set.',
- 'type' => 'checkbox'
- },
-
- {
- 'key' => 'invoice_email_pdf_note',
- 'section' => 'billing',
- 'description' => 'If defined, this text will replace the default plain text invoice as the body of emailed PDF invoices.',
- 'type' => 'textarea'
- },
-
-
- {
- 'key' => 'invoice_default_terms',
- 'section' => 'billing',
- 'description' => 'Optional default invoice term, used to calculate a due date printed on invoices.',
- 'type' => 'select',
- 'select_enum' => [ '', 'Payable upon receipt', 'Net 0', 'Net 10', 'Net 15', 'Net 20', 'Net 30', 'Net 45', 'Net 60' ],
- },
-
- {
- 'key' => 'invoice_sections',
- 'section' => 'billing',
- 'description' => 'Split invoice into sections and label according to package type when enabled.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'payment_receipt_email',
- 'section' => 'billing',
- 'description' => 'Template file for payment receipts. Payment receipts are sent to the customer email invoice destination(s) when a payment is received. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available: <ul><li><code>$date</code> <li><code>$name</code> <li><code>$paynum</code> - Freeside payment number <li><code>$paid</code> - Amount of payment <li><code>$payby</code> - Payment type (Card, Check, Electronic check, etc.) <li><code>$payinfo</code> - Masked credit card number or check number <li><code>$balance</code> - New balance</ul>',
- 'type' => [qw( checkbox textarea )],
- },
-
- {
- 'key' => 'lpr',
- 'section' => 'required',
- 'description' => 'Print command for paper invoices, for example `lpr -h\'',
- 'type' => 'text',
- },
-
- {
- 'key' => 'lpr-postscript_prefix',
- 'section' => 'billing',
- 'description' => 'Raw printer commands prepended to the beginning of postscript print jobs (evaluated as a double-quoted perl string - backslash escapes are available)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'lpr-postscript_suffix',
- 'section' => 'billing',
- 'description' => 'Raw printer commands added to the end of postscript print jobs (evaluated as a double-quoted perl string - backslash escapes are available)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'money_char',
- 'section' => '',
- 'description' => 'Currency symbol - defaults to `$\'',
- 'type' => 'text',
- },
-
- {
- 'key' => 'defaultrecords',
- 'section' => 'BIND',
- 'description' => 'DNS entries to add automatically when creating a domain',
- 'type' => 'editlist',
- 'editlist_parts' => [ { type=>'text' },
- { type=>'immutable', value=>'IN' },
- { type=>'select',
- select_enum=>{ map { $_=>$_ } qw(A CNAME MX NS TXT)} },
- { type=> 'text' }, ],
- },
-
- {
- 'key' => 'passwordmin',
- 'section' => 'password',
- 'description' => 'Minimum password length (default 6)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'passwordmax',
- 'section' => 'password',
- 'description' => 'Maximum password length (default 8) (don\'t set this over 12 if you need to import or export crypt() passwords)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'password-noampersand',
- 'section' => 'password',
- 'description' => 'Disallow ampersands in passwords',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'password-noexclamation',
- 'section' => 'password',
- 'description' => 'Disallow exclamations in passwords (Not setting this could break old text Livingston or Cistron Radius servers)',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'referraldefault',
- 'section' => 'UI',
- 'description' => 'Default referral, specified by refnum',
- 'type' => 'text',
- },
-
-# {
-# 'key' => 'registries',
-# 'section' => 'required',
-# 'description' => 'Directory which contains domain registry information. Each registry is a directory.',
-# },
-
- {
- 'key' => 'maxsearchrecordsperpage',
- 'section' => 'UI',
- 'description' => 'If set, number of search records to return per page.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'session-start',
- 'section' => 'session',
- 'description' => 'If defined, the command which is executed on the Freeside machine when a session begins. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'session-stop',
- 'section' => 'session',
- 'description' => 'If defined, the command which is executed on the Freeside machine when a session ends. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'shells',
- 'section' => 'shell',
- 'description' => 'Legal shells (think /etc/shells). You probably want to `cut -d: -f7 /etc/passwd | sort | uniq\' initially so that importing doesn\'t fail with `Illegal shell\' errors, then remove any special entries afterwords. A blank line specifies that an empty shell is permitted.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'showpasswords',
- 'section' => 'UI',
- 'description' => 'Display unencrypted user passwords in the backend (employee) web interface',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'signupurl',
- 'section' => 'UI',
- 'description' => 'if you are using customer-to-customer referrals, and you enter the URL of your <a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Self-Service_Installation">signup server CGI</a>, the customer view screen will display a customized link to the signup server with the appropriate customer as referral',
- 'type' => 'text',
- },
-
- {
- 'key' => 'smtpmachine',
- 'section' => 'required',
- 'description' => 'SMTP relay for Freeside\'s outgoing mail',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soadefaultttl',
- 'section' => 'BIND',
- 'description' => 'SOA default TTL for new domains.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soaemail',
- 'section' => 'BIND',
- 'description' => 'SOA email for new domains, in BIND form (`.\' instead of `@\'), with trailing `.\'',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soaexpire',
- 'section' => 'BIND',
- 'description' => 'SOA expire for new domains',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soamachine',
- 'section' => 'BIND',
- 'description' => 'SOA machine for new domains, with trailing `.\'',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soarefresh',
- 'section' => 'BIND',
- 'description' => 'SOA refresh for new domains',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soaretry',
- 'section' => 'BIND',
- 'description' => 'SOA retry for new domains',
- 'type' => 'text',
- },
-
- {
- 'key' => 'statedefault',
- 'section' => 'UI',
- 'description' => 'Default state or province (if not supplied, the default is `CA\')',
- 'type' => 'text',
- },
-
- {
- 'key' => 'unsuspendauto',
- 'section' => 'billing',
- 'description' => 'Enables the automatic unsuspension of suspended packages when a customer\'s balance due changes from positive to zero or negative as the result of a payment or credit',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'unsuspend-always_adjust_next_bill_date',
- 'section' => 'billing',
- 'description' => 'Global override that causes unsuspensions to always adjust the next bill date under any circumstances. This is now controlled on a per-package bases - probably best not to use this option unless you are a legacy installation that requires this behaviour.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'usernamemin',
- 'section' => 'username',
- 'description' => 'Minimum username length (default 2)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'usernamemax',
- 'section' => 'username',
- 'description' => 'Maximum username length',
- 'type' => 'text',
- },
-
- {
- 'key' => 'username-ampersand',
- 'section' => 'username',
- 'description' => 'Allow the ampersand character (&amp;) in usernames. Be careful when using this option in conjunction with <a href="../browse/part_export.cgi">exports</a> which execute shell commands, as the ampersand will be interpreted by the shell if not quoted.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-letter',
- 'section' => 'username',
- 'description' => 'Usernames must contain at least one letter',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-letterfirst',
- 'section' => 'username',
- 'description' => 'Usernames must start with a letter',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-noperiod',
- 'section' => 'username',
- 'description' => 'Disallow periods in usernames',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-nounderscore',
- 'section' => 'username',
- 'description' => 'Disallow underscores in usernames',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-nodash',
- 'section' => 'username',
- 'description' => 'Disallow dashes in usernames',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-uppercase',
- 'section' => 'username',
- 'description' => 'Allow uppercase characters in usernames. Not recommended for use with FreeRADIUS with MySQL backend, which is case-insensitive by default.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-percent',
- 'section' => 'username',
- 'description' => 'Allow the percent character (%) in usernames.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'safe-part_bill_event',
- 'section' => 'UI',
- 'description' => 'Validates invoice event expressions against a preset list. Useful for webdemos, annoying to powerusers.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'show_ss',
- 'section' => 'UI',
- 'description' => 'Turns on display/collection of social security numbers in the web interface. Sometimes required by electronic check (ACH) processors.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'show_stateid',
- 'section' => 'UI',
- 'description' => "Turns on display/collection of driver's license/state issued id numbers in the web interface. Sometimes required by electronic check (ACH) processors.",
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'show_bankstate',
- 'section' => 'UI',
- 'description' => "Turns on display/collection of state for bank accounts in the web interface. Sometimes required by electronic check (ACH) processors.",
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'agent_defaultpkg',
- 'section' => 'UI',
- 'description' => 'Setting this option will cause new packages to be available to all agent types by default.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'legacy_link',
- 'section' => 'UI',
- 'description' => 'Display options in the web interface to link legacy pre-Freeside services.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'legacy_link-steal',
- 'section' => 'UI',
- 'description' => 'Allow "stealing" an already-audited service from one customer (or package) to another using the link function.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'queue_dangerous_controls',
- 'section' => 'UI',
- 'description' => 'Enable queue modification controls on account pages and for new jobs. Unless you are a developer working on new export code, you should probably leave this off to avoid causing provisioning problems.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'security_phrase',
- 'section' => 'password',
- 'description' => 'Enable the tracking of a "security phrase" with each account. Not recommended, as it is vulnerable to social engineering.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'locale',
- 'section' => 'UI',
- 'description' => 'Message locale',
- 'type' => 'select',
- 'select_enum' => [ qw(en_US) ],
- },
-
- {
- 'key' => 'signup_server-payby',
- 'section' => '',
- 'description' => 'Acceptable payment types for the signup server',
- 'type' => 'selectmultiple',
- 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY BILL COMP) ],
- },
-
- {
- 'key' => 'signup_server-default_agentnum',
- 'section' => '',
- 'description' => 'Default agent for the signup server',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::agent;
- map { $_->agentnum => $_->agent }
- FS::Record::qsearch('agent', { disabled=>'' } );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::agent;
- my $agent = FS::Record::qsearchs(
- 'agent', { 'agentnum'=>shift }
- );
- $agent ? $agent->agent : '';
- },
- },
-
- {
- 'key' => 'signup_server-default_refnum',
- 'section' => '',
- 'description' => 'Default advertising source for the signup server',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::part_referral;
- map { $_->refnum => $_->referral }
- FS::Record::qsearch( 'part_referral',
- { 'disabled' => '' }
- );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::part_referral;
- my $part_referral = FS::Record::qsearchs(
- 'part_referral', { 'refnum'=>shift } );
- $part_referral ? $part_referral->referral : '';
- },
- },
-
- {
- 'key' => 'signup_server-default_pkgpart',
- 'section' => '',
- 'description' => 'Default pakcage for the signup server',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::part_pkg;
- map { $_->pkgpart => $_->pkg.' - '.$_->comment }
- FS::Record::qsearch( 'part_pkg',
- { 'disabled' => ''}
- );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::part_pkg;
- my $part_pkg = FS::Record::qsearchs(
- 'part_pkg', { 'pkgpart'=>shift }
- );
- $part_pkg
- ? $part_pkg->pkg.' - '.$part_pkg->comment
- : '';
- },
- },
-
- {
- 'key' => 'show-msgcat-codes',
- 'section' => 'UI',
- 'description' => 'Show msgcat codes in error messages. Turn this option on before reporting errors to the mailing list.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'signup_server-realtime',
- 'section' => '',
- 'description' => 'Run billing for signup server signups immediately, and do not provision accounts which subsequently have a balance.',
- 'type' => 'checkbox',
- },
- {
- 'key' => 'signup_server-classnum2',
- 'section' => '',
- 'description' => 'Package Class for first optional purchase',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::pkg_class;
- map { $_->classnum => $_->classname }
- FS::Record::qsearch('pkg_class', {} );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::pkg_class;
- my $pkg_class = FS::Record::qsearchs(
- 'pkg_class', { 'classnum'=>shift }
- );
- $pkg_class ? $pkg_class->classname : '';
- },
- },
-
- {
- 'key' => 'signup_server-classnum3',
- 'section' => '',
- 'description' => 'Package Class for second optional purchase',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::pkg_class;
- map { $_->classnum => $_->classname }
- FS::Record::qsearch('pkg_class', {} );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::pkg_class;
- my $pkg_class = FS::Record::qsearchs(
- 'pkg_class', { 'classnum'=>shift }
- );
- $pkg_class ? $pkg_class->classname : '';
- },
- },
-
- {
- 'key' => 'backend-realtime',
- 'section' => '',
- 'description' => 'Run billing for backend signups immediately.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'declinetemplate',
- 'section' => 'billing',
- 'description' => 'Template file for credit card decline emails.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'emaildecline',
- 'section' => 'billing',
- 'description' => 'Enable emailing of credit card decline notices.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'emaildecline-exclude',
- 'section' => 'billing',
- 'description' => 'List of error messages that should not trigger email decline notices, one per line.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cancelmessage',
- 'section' => 'billing',
- 'description' => 'Template file for cancellation emails.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cancelsubject',
- 'section' => 'billing',
- 'description' => 'Subject line for cancellation emails.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'emailcancel',
- 'section' => 'billing',
- 'description' => 'Enable emailing of cancellation notices. Make sure to fill in the cancelmessage and cancelsubject configuration values as well.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'require_cardname',
- 'section' => 'billing',
- 'description' => 'Require an "Exact name on card" to be entered explicitly; don\'t default to using the first and last name.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'enable_taxclasses',
- 'section' => 'billing',
- 'description' => 'Enable per-package tax classes',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'require_taxclasses',
- 'section' => 'billing',
- 'description' => 'Require a taxclass to be entered for every package',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'welcome_email',
- 'section' => '',
- 'description' => 'Template file for welcome email. Welcome emails are sent to the customer email invoice destination(s) each time a svc_acct record is created. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code></ul>',
- 'type' => 'textarea',
- 'per_agent' => 1,
- },
-
- {
- 'key' => 'welcome_email-from',
- 'section' => '',
- 'description' => 'From: address header for welcome email',
- 'type' => 'text',
- 'per_agent' => 1,
- },
-
- {
- 'key' => 'welcome_email-subject',
- 'section' => '',
- 'description' => 'Subject: header for welcome email',
- 'type' => 'text',
- 'per_agent' => 1,
- },
-
- {
- 'key' => 'welcome_email-mimetype',
- 'section' => '',
- 'description' => 'MIME type for welcome email',
- 'type' => 'select',
- 'select_enum' => [ 'text/plain', 'text/html' ],
- 'per_agent' => 1,
- },
-
- {
- 'key' => 'welcome_letter',
- 'section' => '',
- 'description' => 'Optional LaTex template file for a printed welcome letter. A welcome letter is printed the first time a cust_pkg record is created. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation and the billing documentation for details on the template substitution language. A variable exists for each fieldname in the customer record (<code>$first, $last, etc</code>). The following additional variables are available<ul><li><code>$payby</code> - a friendler represenation of the field<li><code>$payinfo</code> - the masked payment information<li><code>$expdate</code> - the time at which the payment method expires (a UNIX timestamp)<li><code>$returnaddress</code> - the invoice return address for this customer\'s agent</ul>',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'warning_email',
- 'section' => '',
- 'description' => 'Template file for warning email. Warning emails are sent to the customer email invoice destination(s) each time a svc_acct record has its usage drop below a threshold or 0. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code> <li><code>$column</code> <li><code>$amount</code> <li><code>$threshold</code></ul>',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'warning_email-from',
- 'section' => '',
- 'description' => 'From: address header for warning email',
- 'type' => 'text',
- },
-
- {
- 'key' => 'warning_email-cc',
- 'section' => '',
- 'description' => 'Additional recipient(s) (comma separated) for warning email when remaining usage reaches zero.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'warning_email-subject',
- 'section' => '',
- 'description' => 'Subject: header for warning email',
- 'type' => 'text',
- },
-
- {
- 'key' => 'warning_email-mimetype',
- 'section' => '',
- 'description' => 'MIME type for warning email',
- 'type' => 'select',
- 'select_enum' => [ 'text/plain', 'text/html' ],
- },
-
- {
- 'key' => 'payby',
- 'section' => 'billing',
- 'description' => 'Available payment types.',
- 'type' => 'selectmultiple',
- 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD COMP) ],
- },
-
- {
- 'key' => 'payby-default',
- 'section' => 'UI',
- 'description' => 'Default payment type. HIDE disables display of billing information and sets customers to BILL.',
- 'type' => 'select',
- 'select_enum' => [ '', qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD COMP HIDE) ],
- },
-
- {
- 'key' => 'paymentforcedtobatch',
- 'section' => 'UI',
- 'description' => 'Causes per customer payment entry to be forced to a batch processor rather than performed realtime.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-notes',
- 'section' => 'UI',
- 'description' => 'Extra HTML to be displayed on the Account View screen.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'radius-password',
- 'section' => '',
- 'description' => 'RADIUS attribute for plain-text passwords.',
- 'type' => 'select',
- 'select_enum' => [ 'Password', 'User-Password' ],
- },
-
- {
- 'key' => 'radius-ip',
- 'section' => '',
- 'description' => 'RADIUS attribute for IP addresses.',
- 'type' => 'select',
- 'select_enum' => [ 'Framed-IP-Address', 'Framed-Address' ],
- },
-
- {
- 'key' => 'svc_acct-alldomains',
- 'section' => '',
- 'description' => 'Allow accounts to select any domain in the database. Normally accounts can only select from the domain set in the service definition and those purchased by the customer.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'dump-scpdest',
- 'section' => '',
- 'description' => 'destination for scp database dumps: user@host:/path',
- 'type' => 'text',
- },
-
- {
- 'key' => 'dump-pgpid',
- 'section' => '',
- 'description' => "Optional PGP public key user or key id for database dumps. The public key should exist on the freeside user's public keyring, and the gpg binary and GnuPG perl module should be installed.",
- 'type' => 'text',
- },
-
- {
- 'key' => 'cvv-save',
- 'section' => 'billing',
- 'description' => 'Save CVV2 information after the initial transaction for the selected credit card types. Enabling this option may be in violation of your merchant agreement(s), so please check them carefully before enabling this option for any credit card types.',
- 'type' => 'selectmultiple',
- 'select_enum' => \@card_types,
- },
-
- {
- 'key' => 'allow_negative_charges',
- 'section' => 'billing',
- 'description' => 'Allow negative charges. Normally not used unless importing data from a legacy system that requires this.',
- 'type' => 'checkbox',
- },
- {
- 'key' => 'auto_unset_catchall',
- 'section' => '',
- 'description' => 'When canceling a svc_acct that is the email catchall for one or more svc_domains, automatically set their catchall fields to null. If this option is not set, the attempt will simply fail.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'system_usernames',
- 'section' => 'username',
- 'description' => 'A list of system usernames that cannot be edited or removed, one per line. Use a bare username to prohibit modification/deletion of the username in any domain, or username@domain to prohibit modification/deletetion of a specific username and domain.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cust_pkg-change_svcpart',
- 'section' => '',
- 'description' => "When changing packages, move services even if svcparts don't match between old and new pacakge definitions.",
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'disable_autoreverse',
- 'section' => 'BIND',
- 'description' => 'Disable automatic synchronization of reverse-ARPA entries.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_www-enable_subdomains',
- 'section' => '',
- 'description' => 'Enable selection of specific subdomains for virtual host creation.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_www-usersvc_svcpart',
- 'section' => '',
- 'description' => 'Allowable service definition svcparts for virtual hosts, one per line.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'selfservice_server-primary_only',
- 'section' => '',
- 'description' => 'Only allow primary accounts to access self-service functionality.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'card_refund-days',
- 'section' => 'billing',
- 'description' => 'After a payment, the number of days a refund link will be available for that payment. Defaults to 120.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'agent-showpasswords',
- 'section' => '',
- 'description' => 'Display unencrypted user passwords in the agent (reseller) interface',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'global_unique-username',
- 'section' => 'username',
- 'description' => 'Global username uniqueness control: none (usual setting - check uniqueness per exports), username (all usernames are globally unique, regardless of domain or exports), or username@domain (all username@domain pairs are globally unique, regardless of exports). disabled turns off duplicate checking completely and is STRONGLY NOT RECOMMENDED unless you REALLY need to turn this off.',
- 'type' => 'select',
- 'select_enum' => [ 'none', 'username', 'username@domain', 'disabled' ],
- },
-
- {
- 'key' => 'svc_external-skip_manual',
- 'section' => 'UI',
- 'description' => 'When provisioning svc_external services, skip manual entry of id and title fields in the UI. Usually used in conjunction with an export that populates these fields (i.e. artera_turbo).',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_external-display_type',
- 'section' => 'UI',
- 'description' => 'Select a specific svc_external type to enable some UI changes specific to that type (i.e. artera_turbo).',
- 'type' => 'select',
- 'select_enum' => [ 'generic', 'artera_turbo', ],
- },
-
- {
- 'key' => 'ticket_system',
- 'section' => '',
- 'description' => 'Ticketing system integration. <b>RT_Internal</b> uses the built-in RT ticketing system (see the <a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:RT_Installation">integrated ticketing installation instructions</a>). <b>RT_External</b> accesses an external RT installation in a separate database (local or remote).',
- 'type' => 'select',
- #'select_enum' => [ '', qw(RT_Internal RT_Libs RT_External) ],
- 'select_enum' => [ '', qw(RT_Internal RT_External) ],
- },
-
- {
- 'key' => 'ticket_system-default_queueid',
- 'section' => '',
- 'description' => 'Default queue used when creating new customer tickets.',
- 'type' => 'select-sub',
- 'options_sub' => sub {
- my $conf = new FS::Conf;
- if ( $conf->config('ticket_system') ) {
- eval "use FS::TicketSystem;";
- die $@ if $@;
- FS::TicketSystem->queues();
- } else {
- ();
- }
- },
- 'option_sub' => sub {
- my $conf = new FS::Conf;
- if ( $conf->config('ticket_system') ) {
- eval "use FS::TicketSystem;";
- die $@ if $@;
- FS::TicketSystem->queue(shift);
- } else {
- '';
- }
- },
- },
-
- {
- 'key' => 'ticket_system-priority_reverse',
- 'section' => '',
- 'description' => 'Enable this to consider lower numbered priorities more important. A bad habit we picked up somewhere. You probably want to avoid it and use the default.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'ticket_system-custom_priority_field',
- 'section' => '',
- 'description' => 'Custom field from the ticketing system to use as a custom priority classification.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'ticket_system-custom_priority_field-values',
- 'section' => '',
- 'description' => 'Values for the custom field from the ticketing system to break down and sort customer ticket lists.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'ticket_system-custom_priority_field_queue',
- 'section' => '',
- 'description' => 'Ticketing system queue in which the custom field specified in ticket_system-custom_priority_field is located.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'ticket_system-rt_external_datasrc',
- 'section' => '',
- 'description' => 'With external RT integration, the DBI data source for the external RT installation, for example, <code>DBI:Pg:user=rt_user;password=rt_word;host=rt.example.com;dbname=rt</code>',
- 'type' => 'text',
-
- },
-
- {
- 'key' => 'ticket_system-rt_external_url',
- 'section' => '',
- 'description' => 'With external RT integration, the URL for the external RT installation, for example, <code>https://rt.example.com/rt</code>',
- 'type' => 'text',
- },
-
- {
- 'key' => 'company_name',
- 'section' => 'required',
- 'description' => 'Your company name',
- 'type' => 'text',
- },
-
- {
- 'key' => 'company_address',
- 'section' => 'required',
- 'description' => 'Your company address',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'address2-search',
- 'section' => 'UI',
- 'description' => 'Enable a "Unit" search box which searches the second address field. Useful for multi-tenant applications. See also: cust_main-require_address2',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cust_main-require_address2',
- 'section' => 'UI',
- 'description' => 'Second address field is required (on service address only, if billing and service addresses differ). Also enables "Unit" labeling of address2 on customer view and edit pages. Useful for multi-tenant applications. See also: address2-search',
- 'type' => 'checkbox',
- },
-
- { 'key' => 'referral_credit',
- 'section' => 'billing',
- 'description' => "Enables one-time referral credits in the amount of one month <i>referred</i> customer's recurring fee (irregardless of frequency).",
- 'type' => 'checkbox',
- },
-
- { 'key' => 'selfservice_server-cache_module',
- 'section' => '',
- 'description' => 'Module used to store self-service session information. All modules handle any number of self-service servers. Cache::SharedMemoryCache is appropriate for a single database / single Freeside server. Cache::FileCache is useful for multiple databases on a single server, or when IPC::ShareLite is not available (i.e. FreeBSD).', # _Database stores session information in the database and is appropriate for multiple Freeside servers, but may be slower.',
- 'type' => 'select',
- 'select_enum' => [ 'Cache::SharedMemoryCache', 'Cache::FileCache', ], # '_Database' ],
- },
-
- {
- 'key' => 'hylafax',
- 'section' => '',
- 'description' => 'Options for a HylaFAX server to enable the FAX invoice destination. They should be in the form of a space separated list of arguments to the Fax::Hylafax::Client::sendfax subroutine. You probably shouldn\'t override things like \'docfile\'. *Note* Only supported when using typeset invoices (see the invoice_latex configuration option).',
- 'type' => [qw( checkbox textarea )],
- },
-
- {
- 'key' => 'svc_acct-usage_suspend',
- 'section' => 'billing',
- 'description' => 'Suspends the package an account belongs to when svc_acct.seconds or a bytecount is decremented to 0 or below (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-usage_unsuspend',
- 'section' => 'billing',
- 'description' => 'Unuspends the package an account belongs to when svc_acct.seconds or a bytecount is incremented from 0 or below to a positive value (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-usage_threshold',
- 'section' => 'billing',
- 'description' => 'The threshold (expressed as percentage) of acct.seconds or acct.up|down|totalbytes at which a warning message is sent to a service holder. Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd. Defaults to 80.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'cust-fields',
- 'section' => 'UI',
- 'description' => 'Which customer fields to display on reports by default',
- 'type' => 'select',
- 'select_hash' => [ FS::ConfDefaults->cust_fields_avail() ],
- },
-
- {
- 'key' => 'cust_pkg-display_times',
- 'section' => 'UI',
- 'description' => 'Display full timestamps (not just dates) for customer packages. Useful if you are doing real-time things like hourly prepaid.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-edit_uid',
- 'section' => 'shell',
- 'description' => 'Allow UID editing.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-edit_gid',
- 'section' => 'shell',
- 'description' => 'Allow GID editing.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'zone-underscore',
- 'section' => 'BIND',
- 'description' => 'Allow underscores in zone names. As underscores are illegal characters in zone names, this option is not recommended.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'echeck-nonus',
- 'section' => 'billing',
- 'description' => 'Disable ABA-format account checking for Electronic Check payment info',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'voip-cust_cdr_spools',
- 'section' => '',
- 'description' => 'Enable the per-customer option for individual CDR spools.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_forward-arbitrary_dst',
- 'section' => '',
- 'description' => "Allow forwards to point to arbitrary strings that don't necessarily look like email addresses. Only used when using forwards for weird, non-email things.",
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'tax-ship_address',
- 'section' => 'billing',
- 'description' => 'By default, tax calculations are done based on the billing address. Enable this switch to calculate tax based on the shipping address instead. Note: Tax reports can take a long time when enabled.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'batch-enable',
- 'section' => 'billing',
- 'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'batch-default_format',
- 'section' => 'billing',
- 'description' => 'Default format for batches.',
- 'type' => 'select',
- 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch',
- 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP',
- 'ach-spiritone',
- ]
- },
-
- {
- 'key' => 'batch-fixed_format-CARD',
- 'section' => 'billing',
- 'description' => 'Fixed (unchangeable) format for credit card batches.',
- 'type' => 'select',
- 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ,
- 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP' ]
- },
-
- {
- 'key' => 'batch-fixed_format-CHEK',
- 'section' => 'billing',
- 'description' => 'Fixed (unchangeable) format for electronic check batches.',
- 'type' => 'select',
- 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP',
- 'ach-spiritone',
- ]
- },
-
- {
- 'key' => 'batch-increment_expiration',
- 'section' => 'billing',
- 'description' => 'Increment expiration date years in batches until cards are current. Make sure this is acceptable to your batching provider before enabling.',
- 'type' => 'checkbox'
- },
-
- {
- 'key' => 'batchconfig-BoM',
- 'section' => 'billing',
- 'description' => 'Configuration for Bank of Montreal batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'batchconfig-PAP',
- 'section' => 'billing',
- 'description' => 'Configuration for PAP batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'batchconfig-csv-chase_canada-E-xactBatch',
- 'section' => 'billing',
- 'description' => 'Gateway ID for Chase Canada E-xact batching',
- 'type' => 'text',
- },
-
- {
- 'key' => 'payment_history-years',
- 'section' => 'UI',
- 'description' => 'Number of years of payment history to show by default. Currently defaults to 2.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'cust_main-use_comments',
- 'section' => 'UI',
- 'description' => 'Display free form comments on the customer edit screen. Useful as a scratch pad.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cust_main-disable_notes',
- 'section' => 'UI',
- 'description' => 'Disable new style customer notes - timestamped and user identified customer notes. Useful in tracking who did what.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cust_main_note-display_times',
- 'section' => 'UI',
- 'description' => 'Display full timestamps (not just dates) for customer notes.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cust_main-ticket_statuses',
- 'section' => 'UI',
- 'description' => 'Show tickets with these statuses on the customer view page.',
- 'type' => 'selectmultiple',
- 'select_enum' => [qw( new open stalled resolved rejected deleted )],
- },
-
- {
- 'key' => 'cust_main-max_tickets',
- 'section' => 'UI',
- 'description' => 'Maximum number of tickets to show on the customer view page.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'cust_main-skeleton_tables',
- 'section' => '',
- 'description' => 'Tables which will have skeleton records inserted into them for each customer. Syntax for specifying tables is unfortunately a tricky perl data structure for now.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cust_main-skeleton_custnum',
- 'section' => '',
- 'description' => 'Customer number specifying the source data to copy into skeleton tables for new customers.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'cust_main-enable_birthdate',
- 'section' => 'UI',
- 'descritpion' => 'Enable tracking of a birth date with each customer record',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'support-key',
- 'section' => '',
- 'description' => 'A support key enables access to commercial services delivered over the network, such as the payroll module, access to the internal ticket system, priority support and optional backups.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'card-types',
- 'section' => 'billing',
- 'description' => 'Select one or more card types to enable only those card types. If no card types are selected, all card types are available.',
- 'type' => 'selectmultiple',
- 'select_enum' => \@card_types,
- },
-
- {
- 'key' => 'disable-fuzzy',
- 'section' => 'UI',
- 'description' => 'Disable fuzzy searching. Speeds up searching for large sites, but only shows exact matches.',
- 'type' => 'checkbox',
- },
-
- { 'key' => 'pkg_referral',
- 'section' => '',
- 'description' => 'Enable package-specific advertising sources.',
- 'type' => 'checkbox',
- },
-
- { 'key' => 'pkg_referral-multiple',
- 'section' => '',
- 'description' => 'In addition, allow multiple advertising sources to be associated with a single package.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'dashboard-toplist',
- 'section' => 'UI',
- 'description' => 'List of items to display on the top of the front page',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'impending_recur_template',
- 'section' => 'billing',
- 'description' => 'Template file for alerts about looming first time recurrant billing. See the <a href="http://search.cpan.org/~mjd/Text-Template.pm">Text::Template</a> documentation for details on the template substitition language. Also see packages with a <a href="../browse/part_pkg.cgi">flat price plan</a> The following variables are available<ul><li><code>$packages</code> allowing <code>$packages->[0]</code> thru <code>$packages->[n]</code> <li><code>$package</code> the first package, same as <code>$packages->[0]</code> <li><code>$recurdates</code> allowing <code>$recurdates->[0]</code> thru <code>$recurdates->[n]</code> <li><code>$recurdate</code> the first recurdate, same as <code>$recurdate->[0]</code> <li><code>$first</code> <li><code>$last</code></ul>',
-# <li><code>$payby</code> <li><code>$expdate</code> most likely only confuse
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'logo.png',
- 'section' => 'billing', #?
- 'description' => 'An image to include in some types of invoices',
- 'type' => 'binary',
- },
-
- {
- 'key' => 'logo.eps',
- 'section' => 'billing', #?
- 'description' => 'An image to include in some types of invoices',
- 'type' => 'binary',
- },
-
- {
- 'key' => 'selfservice-ignore_quantity',
- 'section' => '',
- 'description' => 'Ignores service quantity restrictions in self-service context. Strongly not recommended - just set your quantities correctly in the first place.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'selfservice-session_timeout',
- 'section' => '',
- 'description' => 'Self-service session timeout. Defaults to 1 hour.',
- 'type' => 'select',
- 'select_enum' => [ '1 hour', '2 hours', '4 hours', '8 hours', '1 day', '1 week', ],
- },
-
- {
- 'key' => 'disable_setup_suspended_pkgs',
- 'section' => 'billing',
- 'description' => 'Disables charging of setup fees for suspended packages.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'password-generated-allcaps',
- 'section' => 'password',
- 'description' => 'Causes passwords automatically generated to consist entirely of capital letters',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'datavolume-forcemegabytes',
- 'section' => 'UI',
- 'description' => 'All data volumes are expressed in megabytes',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'datavolume-significantdigits',
- 'section' => 'UI',
- 'description' => 'number of significant digits to use to represent data volumes',
- 'type' => 'text',
- },
-
- {
- 'key' => 'disable_void_after',
- 'section' => 'billing',
- 'description' => 'Number of seconds after which freeside won\'t attempt to VOID a payment first when performing a refund.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'disable_line_item_date_ranges',
- 'section' => 'billing',
- 'description' => 'Prevent freeside from automatically generating date ranges on invoice line items.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'support_packages',
- 'section' => '',
- 'description' => 'A list of packages eligible for RT ticket time transfer, one pkgpart per line.', #this should really be a select multiple, or specified in the packages themselves...
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cust_main-require_phone',
- 'section' => '',
- 'description' => 'Require daytime or night for all customer records.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cust_main-require_invoicing_list_email',
- 'section' => '',
- 'description' => 'Email address field is required: require at least one invoicing email address for all customer records.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-display_paid_time_remaining',
- 'section' => '',
- 'description' => 'Show paid time remaining in addition to time remaining.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cancel_credit_type',
- 'section' => 'billing',
- 'description' => 'The group to use for new, automatically generated credit reasons resulting from cancellation.',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::reason_type;
- map { $_->typenum => $_->type }
- FS::Record::qsearch('reason_type', { class=>'R' } );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::reason_type;
- my $reason_type = FS::Record::qsearchs(
- 'reason_type', { 'typenum' => shift }
- );
- $reason_type ? $reason_type->type : '';
- },
- },
-
- {
- 'key' => 'referral_credit_type',
- 'section' => 'billing',
- 'description' => 'The group to use for new, automatically generated credit reasons resulting from referrals.',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::reason_type;
- map { $_->typenum => $_->type }
- FS::Record::qsearch('reason_type', { class=>'R' } );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::reason_type;
- my $reason_type = FS::Record::qsearchs(
- 'reason_type', { 'typenum' => shift }
- );
- $reason_type ? $reason_type->type : '';
- },
- },
-
- {
- 'key' => 'signup_credit_type',
- 'section' => 'billing',
- 'description' => 'The group to use for new, automatically generated credit reasons resulting from signup and self-service declines.',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::reason_type;
- map { $_->typenum => $_->type }
- FS::Record::qsearch('reason_type', { class=>'R' } );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::reason_type;
- my $reason_type = FS::Record::qsearchs(
- 'reason_type', { 'typenum' => shift }
- );
- $reason_type ? $reason_type->type : '';
- },
- },
-
- {
- 'key' => 'cust_main-agent_custid-format',
- 'section' => '',
- 'description' => 'Enables searching of various formatted values in cust_main.agent_custid',
- 'type' => 'select',
- 'select_hash' => [
- '' => 'Numeric only',
- 'ww?d+' => 'Numeric with one or two letter prefix',
- ],
- },
-
- {
- 'key' => 'card_masking_method',
- 'section' => 'UI',
- 'description' => 'Digits to display when masking credit cards. Note that the first six digits are necessary to canonically identify the credit card type (Visa/MC, Amex, Discover, Maestro, etc.) in all cases. The first four digits can identify the most common credit card types in most cases (Visa/MC, Amex, and Discover). The first two digits can distinguish between Visa/MC and Amex.',
- 'type' => 'select',
- 'select_hash' => [
- '' => '123456xxxxxx1234',
- 'first6last2' => '123456xxxxxxxx12',
- 'first4last4' => '1234xxxxxxxx1234',
- 'first4last2' => '1234xxxxxxxxxx12',
- 'first2last4' => '12xxxxxxxxxx1234',
- 'first2last2' => '12xxxxxxxxxxxx12',
- 'first0last4' => 'xxxxxxxxxxxx1234',
- 'first0last2' => 'xxxxxxxxxxxxxx12',
- ],
- },
-
-);
-
-1;
diff --git a/FS/FS/ConfDefaults.pm b/FS/FS/ConfDefaults.pm
deleted file mode 100644
index 7978259..0000000
--- a/FS/FS/ConfDefaults.pm
+++ /dev/null
@@ -1,73 +0,0 @@
-package FS::ConfDefaults;
-
-=head1 NAME
-
-FS::ConfDefaults - Freeside configuration default and available values
-
-=head1 SYNOPSIS
-
- use FS::ConfDefaults;
-
- @avail_cust_fields = FS::ConfDefaults->cust_fields_avail();
-
-=head1 DESCRIPTION
-
-Just a small class to keep config default and available values
-
-=head1 METHODS
-
-=over 4
-
-=item cust_fields_avail
-
-Returns a list, suitable for assigning to a hash, of available values and
-labels for customer fields values.
-
-=cut
-
-# XXX should use msgcat for "Day phone" and "Night phone", but how?
-sub cust_fields_avail { (
-
- 'Cust. Status | Customer' =>
- 'Status | Last, First or Company (Last, First)',
- 'Cust# | Cust. Status | Customer' =>
- 'custnum | Status | Last, First or Company (Last, First)',
-
- 'Cust. Status | Name | Company' =>
- 'Status | Last, First | Company',
- 'Cust# | Cust. Status | Name | Company' =>
- 'custnum | Status | Last, First | Company',
-
- 'Cust. Status | (bill) Customer | (service) Customer' =>
- 'Status | Last, First or Company (Last, First) | (same for service contact if present)',
- 'Cust# | Cust. Status | (bill) Customer | (service) Customer' =>
- 'custnum | Status | Last, First or Company (Last, First) | (same for service contact if present)',
-
- 'Cust. Status | (bill) Name | (bill) Company | (service) Name | (service) Company' =>
- 'Status | Last, First | Company | (same for service address if present)',
- 'Cust# | Cust. Status | (bill) Name | (bill) Company | (service) Name | (service) Company' =>
- 'custnum | Status | Last, First | Company | (same for service address if present)',
-
- 'Cust# | Cust. Status | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Invoicing email(s)' =>
- 'custnum | Status | Last, First | Company | (all address fields ) | Day phone | Night phone | Invoicing email(s)',
-
- 'Cust# | Cust. Status | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Fax number | Invoicing email(s) | Payment Type' =>
- 'custnum | Status | Last, First | Company | (all address fields ) | ( all phones ) | Invoicing email(s) | Payment Type',
- 'Cust# | Cust. Status | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Fax number | Invoicing email(s) | Payment Type | Current Balance' =>
- 'custnum | Status | Last, First | Company | (all address fields ) | ( all phones ) | Invoicing email(s) | Payment Type | Current Balance',
-
-); }
-
-=back
-
-=head1 BUGS
-
-Not yet.
-
-=head1 SEE ALSO
-
-L<FS::Conf>
-
-=cut
-
-1;
diff --git a/FS/FS/ConfItem.pm b/FS/FS/ConfItem.pm
deleted file mode 100644
index a0e997a..0000000
--- a/FS/FS/ConfItem.pm
+++ /dev/null
@@ -1,63 +0,0 @@
-package FS::ConfItem;
-
-=head1 NAME
-
-FS::ConfItem - Configuration option meta-data.
-
-=head1 SYNOPSIS
-
- use FS::Conf;
- @config_items = $conf->config_items;
-
- foreach $item ( @config_items ) {
- $key = $item->key;
- $section = $item->section;
- $description = $item->description;
- }
-
-=head1 DESCRIPTION
-
-=head1 METHODS
-
-=over 4
-
-=item new
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = @_ ? shift : {};
- bless ($self, $class);
-}
-
-=item key
-
-=item section
-
-=item description
-
-=cut
-
-sub AUTOLOAD {
- my $self = shift;
- my $field = $AUTOLOAD;
- $field =~ s/.*://;
- $self->{$field};
-}
-
-=back
-
-=head1 BUGS
-
-Terse docs.
-
-=head1 SEE ALSO
-
-L<FS::Conf>
-
-=cut
-
-1;
-
diff --git a/FS/FS/Conf_compat17.pm b/FS/FS/Conf_compat17.pm
deleted file mode 100644
index bcd78e8..0000000
--- a/FS/FS/Conf_compat17.pm
+++ /dev/null
@@ -1,2196 +0,0 @@
-package FS::Conf_compat17;
-
-use vars qw($default_dir $base_dir @config_items @card_types $DEBUG );
-use IO::File;
-use File::Basename;
-use FS::ConfItem;
-use FS::ConfDefaults;
-
-$base_dir = '%%%FREESIDE_CONF%%%';
-$default_dir = '%%%FREESIDE_CONF%%%';
-
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::Conf - Freeside configuration values
-
-=head1 SYNOPSIS
-
- use FS::Conf;
-
- $conf = new FS::Conf "/config/directory";
-
- $FS::Conf::default_dir = "/config/directory";
- $conf = new FS::Conf;
-
- $dir = $conf->dir;
-
- $value = $conf->config('key');
- @list = $conf->config('key');
- $bool = $conf->exists('key');
-
- $conf->touch('key');
- $conf->set('key' => 'value');
- $conf->delete('key');
-
- @config_items = $conf->config_items;
-
-=head1 DESCRIPTION
-
-Read and write Freeside configuration values. Keys currently map to filenames,
-but this may change in the future.
-
-=head1 METHODS
-
-=over 4
-
-=item new [ DIRECTORY ]
-
-Create a new configuration object. A directory arguement is required if
-$FS::Conf::default_dir has not been set.
-
-=cut
-
-sub new {
- my($proto,$dir) = @_;
- my($class) = ref($proto) || $proto;
- my($self) = { 'dir' => $dir || $default_dir,
- 'base_dir' => $base_dir,
- };
- bless ($self, $class);
-}
-
-=item dir
-
-Returns the conf directory.
-
-=cut
-
-sub dir {
- my($self) = @_;
- my $dir = $self->{dir};
- -e $dir or die "FATAL: $dir doesn't exist!";
- -d $dir or die "FATAL: $dir isn't a directory!";
- -r $dir or die "FATAL: Can't read $dir!";
- -x $dir or die "FATAL: $dir not searchable (executable)!";
- $dir =~ /^(.*)$/;
- $1;
-}
-
-=item base_dir
-
-Returns the base directory. By default this is /usr/local/etc/freeside.
-
-=cut
-
-sub base_dir {
- my($self) = @_;
- my $base_dir = $self->{base_dir};
- -e $base_dir or die "FATAL: $base_dir doesn't exist!";
- -d $base_dir or die "FATAL: $base_dir isn't a directory!";
- -r $base_dir or die "FATAL: Can't read $base_dir!";
- -x $base_dir or die "FATAL: $base_dir not searchable (executable)!";
- $base_dir =~ /^(.*)$/;
- $1;
-}
-
-=item config KEY
-
-Returns the configuration value or values (depending on context) for key.
-
-=cut
-
-sub config {
- my($self,$file)=@_;
- my($dir)=$self->dir;
- my $fh = new IO::File "<$dir/$file" or return;
- if ( wantarray ) {
- map {
- /^(.*)$/
- or die "Illegal line (array context) in $dir/$file:\n$_\n";
- $1;
- } <$fh>;
- } else {
- <$fh> =~ /^(.*)$/
- or die "Illegal line (scalar context) in $dir/$file:\n$_\n";
- $1;
- }
-}
-
-=item config_binary KEY
-
-Returns the exact scalar value for key.
-
-=cut
-
-sub config_binary {
- my($self,$file)=@_;
- my($dir)=$self->dir;
- my $fh = new IO::File "<$dir/$file" or return;
- local $/;
- my $content = <$fh>;
- $content;
-}
-
-=item exists KEY
-
-Returns true if the specified key exists, even if the corresponding value
-is undefined.
-
-=cut
-
-sub exists {
- my($self,$file)=@_;
- my($dir) = $self->dir;
- -e "$dir/$file";
-}
-
-=item config_orbase KEY SUFFIX
-
-Returns the configuration value or values (depending on context) for
-KEY_SUFFIX, if it exists, otherwise for KEY
-
-=cut
-
-sub config_orbase {
- my( $self, $file, $suffix ) = @_;
- if ( $self->exists("${file}_$suffix") ) {
- $self->config("${file}_$suffix");
- } else {
- $self->config($file);
- }
-}
-
-=item touch KEY
-
-Creates the specified configuration key if it does not exist.
-
-=cut
-
-sub touch {
- my($self, $file) = @_;
- my $dir = $self->dir;
- unless ( $self->exists($file) ) {
- warn "[FS::Conf] TOUCH $file\n" if $DEBUG;
- system('touch', "$dir/$file");
- }
-}
-
-=item set KEY VALUE
-
-Sets the specified configuration key to the given value.
-
-=cut
-
-sub set {
- my($self, $file, $value) = @_;
- my $dir = $self->dir;
- $value =~ /^(.*)$/s;
- $value = $1;
- unless ( join("\n", @{[ $self->config($file) ]}) eq $value ) {
- warn "[FS::Conf] SET $file\n" if $DEBUG;
-# warn "$dir" if is_tainted($dir);
-# warn "$dir" if is_tainted($file);
- chmod 0644, "$dir/$file";
- my $fh = new IO::File ">$dir/$file" or return;
- chmod 0644, "$dir/$file";
- print $fh "$value\n";
- }
-}
-#sub is_tainted {
-# return ! eval { join('',@_), kill 0; 1; };
-# }
-
-=item delete KEY
-
-Deletes the specified configuration key.
-
-=cut
-
-sub delete {
- my($self, $file) = @_;
- my $dir = $self->dir;
- if ( $self->exists($file) ) {
- warn "[FS::Conf] DELETE $file\n";
- unlink "$dir/$file";
- }
-}
-
-=item config_items
-
-Returns all of the possible configuration items as FS::ConfItem objects. See
-L<FS::ConfItem>.
-
-=cut
-
-sub config_items {
- my $self = shift;
- #quelle kludge
- @config_items,
- ( map {
- my $basename = basename($_);
- $basename =~ /^(.*)$/;
- $basename = $1;
- new FS::ConfItem {
- 'key' => $basename,
- 'section' => 'billing',
- 'description' => 'Alternate template file for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
- 'type' => 'textarea',
- }
- } glob($self->dir. '/invoice_template_*')
- ),
- ( map {
- my $basename = basename($_);
- $basename =~ /^(.*)$/;
- $basename = $1;
- new FS::ConfItem {
- 'key' => $basename,
- 'section' => 'billing',
- 'description' => 'Alternate HTML template for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
- 'type' => 'textarea',
- }
- } glob($self->dir. '/invoice_html_*')
- ),
- ( map {
- my $basename = basename($_);
- $basename =~ /^(.*)$/;
- $basename = $1;
- ($latexname = $basename ) =~ s/latex/html/;
- new FS::ConfItem {
- 'key' => $basename,
- 'section' => 'billing',
- 'description' => "Alternate Notes section for HTML invoices. Defaults to the same data in $latexname if not specified.",
- 'type' => 'textarea',
- }
- } glob($self->dir. '/invoice_htmlnotes_*')
- ),
- ( map {
- my $basename = basename($_);
- $basename =~ /^(.*)$/;
- $basename = $1;
- new FS::ConfItem {
- 'key' => $basename,
- 'section' => 'billing',
- 'description' => 'Alternate LaTeX template for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
- 'type' => 'textarea',
- }
- } glob($self->dir. '/invoice_latex_*')
- ),
- ( map {
- my $basename = basename($_);
- $basename =~ /^(.*)$/;
- $basename = $1;
- new FS::ConfItem {
- 'key' => $basename,
- 'section' => 'billing',
- 'description' => 'Alternate Notes section for LaTeX typeset PostScript invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
- 'type' => 'textarea',
- }
- } glob($self->dir. '/invoice_latexnotes_*')
- );
-}
-
-=back
-
-=head1 BUGS
-
-If this was more than just crud that will never be useful outside Freeside I'd
-worry that config_items is freeside-specific and icky.
-
-=head1 SEE ALSO
-
-"Configuration" in the web interface (config/config.cgi).
-
-httemplate/docs/config.html
-
-=cut
-
-#Business::CreditCard
-@card_types = (
- "VISA card",
- "MasterCard",
- "Discover card",
- "American Express card",
- "Diner's Club/Carte Blanche",
- "enRoute",
- "JCB",
- "BankCard",
- "Switch",
- "Solo",
-);
-
-@config_items = map { new FS::ConfItem $_ } (
-
- {
- 'key' => 'address',
- 'section' => 'deprecated',
- 'description' => 'This configuration option is no longer used. See <a href="#invoice_template">invoice_template</a> instead.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'alerter_template',
- 'section' => 'billing',
- 'description' => 'Template file for billing method expiration alerts. See the <a href="../docs/billing.html#invoice_template">billing documentation</a> for details.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'apacheroot',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>www_shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. The directory containing Apache virtual hosts',
- 'type' => 'text',
- },
-
- {
- 'key' => 'apacheip',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add an <i>apache</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be the current IP address to assign to new virtual hosts',
- 'type' => 'text',
- },
-
- {
- 'key' => 'apachemachine',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>www_shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. A machine with the apacheroot directory and user home directories. The existance of this file enables setup of virtual host directories, and, in conjunction with the `home\' configuration file, symlinks into user home directories.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'apachemachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add an <i>apache</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be Apache machines, one per line. This enables export of `/etc/apache/vhosts.conf\', which can be included in your Apache configuration via the <a href="http://www.apache.org/docs/mod/core.html#include">Include</a> directive.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'bindprimary',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>bind</i> <a href="../browse/part_export.cgi">export</a> instead. Your BIND primary nameserver. This enables export of /var/named/named.conf and zone files into /var/named',
- 'type' => 'text',
- },
-
- {
- 'key' => 'bindsecondaries',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>bind_slave</i> <a href="../browse/part_export.cgi">export</a> instead. Your BIND secondary nameservers, one per line. This enables export of /var/named/named.conf',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'encryption',
- 'section' => 'billing',
- 'description' => 'Enable encryption of credit cards.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'encryptionmodule',
- 'section' => 'billing',
- 'description' => 'Use which module for encryption?',
- 'type' => 'text',
- },
-
- {
- 'key' => 'encryptionpublickey',
- 'section' => 'billing',
- 'description' => 'Your RSA Public Key - Required if Encryption is turned on.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'encryptionprivatekey',
- 'section' => 'billing',
- 'description' => 'Your RSA Private Key - Including this will enable the "Bill Now" feature. However if the system is compromised, a hacker can use this key to decode the stored credit card information. This is generally not a good idea.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'business-onlinepayment',
- 'section' => 'billing',
- 'description' => '<a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support, at least three lines: processor, login, and password. An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\'). Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'business-onlinepayment-ach',
- 'section' => 'billing',
- 'description' => 'Alternate <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support for ACH transactions (defaults to regular <b>business-onlinepayment</b>). At least three lines: processor, login, and password. An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\'). Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'business-onlinepayment-description',
- 'section' => 'billing',
- 'description' => 'String passed as the description field to <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a>. Evaluated as a double-quoted perl string, with the following variables available: <code>$agent</code> (the agent name), and <code>$pkgs</code> (a comma-separated list of packages for which these charges apply)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'business-onlinepayment-email-override',
- 'section' => 'billing',
- 'description' => 'Email address used instead of customer email address when submitting a BOP transaction.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'bsdshellmachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>bsdshell</i> <a href="../browse/part_export.cgi">export</a> instead. Your BSD flavored shell (and mail) machines, one per line. This enables export of `/etc/passwd\' and `/etc/master.passwd\'.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'countrydefault',
- 'section' => 'UI',
- 'description' => 'Default two-letter country code (if not supplied, the default is `US\')',
- 'type' => 'text',
- },
-
- {
- 'key' => 'date_format',
- 'section' => 'UI',
- 'description' => 'Format for displaying dates',
- 'type' => 'select',
- 'select_hash' => [
- '%m/%d/%Y' => 'MM/DD/YYYY',
- '%Y/%m/%d' => 'YYYY/MM/DD',
- ],
- },
-
- {
- 'key' => 'cyrus',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>cyrus</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to integrate with <a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a>, three lines: IMAP server, admin username, and admin password. Cyrus::IMAP::Admin should be installed locally and the connection to the server secured.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cp_app',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>cp</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to integrate with <a href="http://www.cp.net/">Critial Path Account Provisioning Protocol</a>, four lines: "host:port", username, password, and workgroup (for new users).',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'deletecustomers',
- 'section' => 'UI',
- 'description' => 'Enable customer deletions. Be very careful! Deleting a customer will remove all traces that this customer ever existed! It should probably only be used when auditing a legacy database. Normally, you cancel all of a customers\' packages if they cancel service.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'deletepayments',
- 'section' => 'billing',
- 'description' => 'Enable deletion of unclosed payments. Really, with voids this is pretty much not recommended in any situation anymore. Be very careful! Only delete payments that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.',
- 'type' => [qw( checkbox text )],
- },
-
- {
- 'key' => 'deletecredits',
- 'section' => 'deprecated',
- 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.',
- 'type' => [qw( checkbox text )],
- },
-
- {
- 'key' => 'deleterefunds',
- 'section' => 'billing',
- 'description' => 'Enable deletion of unclosed refunds. Be very careful! Only delete refunds that were data-entry errors, not adjustments.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'unapplypayments',
- 'section' => 'deprecated',
- 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable "unapplication" of unclosed payments.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'unapplycredits',
- 'section' => 'deprecated',
- 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to nable "unapplication" of unclosed credits.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'dirhash',
- 'section' => 'shell',
- 'description' => 'Optional numeric value to control directory hashing. If positive, hashes directories for the specified number of levels from the front of the username. If negative, hashes directories for the specified number of levels from the end of the username. Some examples: <ul><li>1: user -> <a href="#home">/home</a>/u/user<li>2: user -> <a href="#home">/home</a>/u/s/user<li>-1: user -> <a href="#home">/home</a>/r/user<li>-2: user -> <a href="#home">home</a>/r/e/user</ul>',
- 'type' => 'text',
- },
-
- {
- 'key' => 'disable_customer_referrals',
- 'section' => 'UI',
- 'description' => 'Disable new customer-to-customer referrals in the web interface',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'editreferrals',
- 'section' => 'UI',
- 'description' => 'Enable advertising source modification for existing customers',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'emailinvoiceonly',
- 'section' => 'billing',
- 'description' => 'Disables postal mail invoices',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'disablepostalinvoicedefault',
- 'section' => 'billing',
- 'description' => 'Disables postal mail invoices as the default option in the UI. Be careful not to setup customers which are not sent invoices. See <a href ="#emailinvoiceauto">emailinvoiceauto</a>.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'emailinvoiceauto',
- 'section' => 'billing',
- 'description' => 'Automatically adds new accounts to the email invoice list',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'emailinvoiceautoalways',
- 'section' => 'billing',
- 'description' => 'Automatically adds new accounts to the email invoice list even when the list contains email addresses',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'exclude_ip_addr',
- 'section' => '',
- 'description' => 'Exclude these from the list of available broadband service IP addresses. (One per line)',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'erpcdmachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, ERPCD is no longer supported. Used to be ERPCD authentication machines, one per line. This enables export of `/usr/annex/acp_passwd\' and `/usr/annex/acp_dialup\'',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'hidecancelledpackages',
- 'section' => 'UI',
- 'description' => 'Prevent cancelled packages from showing up in listings (though they will still be in the database)',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'hidecancelledcustomers',
- 'section' => 'UI',
- 'description' => 'Prevent customers with only cancelled packages from showing up in listings (though they will still be in the database)',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'home',
- 'section' => 'required',
- 'description' => 'For new users, prefixed to username to create a directory name. Should have a leading but not a trailing slash.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'icradiusmachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to enable radcheck and radreply table population - by default in the Freeside database, or in the database specified by the <a href="http://rootwood.haze.st/aspside/config/config-view.cgi#icradius_secrets">icradius_secrets</a> config option (the radcheck and radreply tables needs to be created manually). You do not need to use MySQL for your Freeside database to export to an ICRADIUS/FreeRADIUS MySQL database with this option. <blockquote><b>ADDITIONAL DEPRECATED FUNCTIONALITY</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - your <a href="ftp://ftp.cheapnet.net/pub/icradius">ICRADIUS</a> machines or <a href="http://www.freeradius.org/">FreeRADIUS</a> (with MySQL authentication) machines, one per line. Machines listed in this file will have the radcheck table exported to them. Each line should contain four items, separted by whitespace: machine name, MySQL database name, MySQL username, and MySQL password. For example: <CODE>"radius.isp.tld&nbsp;radius_db&nbsp;radius_user&nbsp;passw0rd"</CODE></blockquote>',
- 'type' => [qw( checkbox textarea )],
- },
-
- {
- 'key' => 'icradius_mysqldest',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be the destination directory for the MySQL databases, on the ICRADIUS/FreeRADIUS machines. Defaults to "/usr/local/var/".',
- 'type' => 'text',
- },
-
- {
- 'key' => 'icradius_mysqlsource',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be the source directory for for the MySQL radcheck table files, on the Freeside machine. Defaults to "/usr/local/var/freeside".',
- 'type' => 'text',
- },
-
- {
- 'key' => 'icradius_secrets',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to specify a database for ICRADIUS/FreeRADIUS export. Three lines: DBI data source, username and password.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_from',
- 'section' => 'required',
- 'description' => 'Return address on email invoices',
- 'type' => 'text',
- },
-
- {
- 'key' => 'invoice_template',
- 'section' => 'required',
- 'description' => 'Required template file for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_html',
- 'section' => 'billing',
- 'description' => 'Optional HTML template for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
-
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_htmlnotes',
- 'section' => 'billing',
- 'description' => 'Notes section for HTML invoices. Defaults to the same data in invoice_latexnotes if not specified.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_htmlfooter',
- 'section' => 'billing',
- 'description' => 'Footer for HTML invoices. Defaults to the same data in invoice_latexfooter if not specified.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_htmlreturnaddress',
- 'section' => 'billing',
- 'description' => 'Return address for HTML invoices. Defaults to the same data in invoice_latexreturnaddress if not specified.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latex',
- 'section' => 'billing',
- 'description' => 'Optional LaTeX template for typeset PostScript invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latexnotes',
- 'section' => 'billing',
- 'description' => 'Notes section for LaTeX typeset PostScript invoices.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latexfooter',
- 'section' => 'billing',
- 'description' => 'Footer for LaTeX typeset PostScript invoices.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latexreturnaddress',
- 'section' => 'billing',
- 'description' => 'Return address for LaTeX typeset PostScript invoices.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_latexsmallfooter',
- 'section' => 'billing',
- 'description' => 'Optional small footer for multi-page LaTeX typeset PostScript invoices.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'invoice_email_pdf',
- 'section' => 'billing',
- 'description' => 'Send PDF invoice as an attachment to emailed invoices. By default, includes the plain text invoice as the email body, unless invoice_email_pdf_note is set.',
- 'type' => 'checkbox'
- },
-
- {
- 'key' => 'invoice_email_pdf_note',
- 'section' => 'billing',
- 'description' => 'If defined, this text will replace the default plain text invoice as the body of emailed PDF invoices.',
- 'type' => 'textarea'
- },
-
-
- {
- 'key' => 'invoice_default_terms',
- 'section' => 'billing',
- 'description' => 'Optional default invoice term, used to calculate a due date printed on invoices.',
- 'type' => 'select',
- 'select_enum' => [ '', 'Payable upon receipt', 'Net 0', 'Net 10', 'Net 15', 'Net 30', 'Net 45', 'Net 60' ],
- },
-
- {
- 'key' => 'invoice_send_receipts',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, this used to send an invoice copy on payments and credits. See the payment_receipt_email and XXXX instead.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'payment_receipt_email',
- 'section' => 'billing',
- 'description' => 'Template file for payment receipts. Payment receipts are sent to the customer email invoice destination(s) when a payment is received. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available: <ul><li><code>$date</code> <li><code>$name</code> <li><code>$paynum</code> - Freeside payment number <li><code>$paid</code> - Amount of payment <li><code>$payby</code> - Payment type (Card, Check, Electronic check, etc.) <li><code>$payinfo</code> - Masked credit card number or check number <li><code>$balance</code> - New balance</ul>',
- 'type' => [qw( checkbox textarea )],
- },
-
- {
- 'key' => 'lpr',
- 'section' => 'required',
- 'description' => 'Print command for paper invoices, for example `lpr -h\'',
- 'type' => 'text',
- },
-
- {
- 'key' => 'maildisablecatchall',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, now the default. Turning this option on used to disable the requirement that each virtual domain have a catch-all mailbox.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'lpr-postscript_prefix',
- 'section' => 'billing',
- 'description' => 'Raw printer commands prepended to the beginning of postscript print jobs (evaluated as a double-quoted perl string - backslash escapes are available)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'lpr-postscript_suffix',
- 'section' => 'billing',
- 'description' => 'Raw printer commands added to the end of postscript print jobs (evaluated as a double-quoted perl string - backslash escapes are available)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'money_char',
- 'section' => '',
- 'description' => 'Currency symbol - defaults to `$\'',
- 'type' => 'text',
- },
-
- {
- 'key' => 'mxmachines',
- 'section' => 'deprecated',
- 'description' => 'MX entries for new domains, weight and machine, one per line, with trailing `.\'',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'nsmachines',
- 'section' => 'deprecated',
- 'description' => 'NS nameservers for new domains, one per line, with trailing `.\'',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'defaultrecords',
- 'section' => 'BIND',
- 'description' => 'DNS entries to add automatically when creating a domain',
- 'type' => 'editlist',
- 'editlist_parts' => [ { type=>'text' },
- { type=>'immutable', value=>'IN' },
- { type=>'select',
- select_enum=>{ map { $_=>$_ } qw(A CNAME MX NS TXT)} },
- { type=> 'text' }, ],
- },
-
- {
- 'key' => 'arecords',
- 'section' => 'deprecated',
- 'description' => 'A list of tab seperated CNAME records to add automatically when creating a domain',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cnamerecords',
- 'section' => 'deprecated',
- 'description' => 'A list of tab seperated CNAME records to add automatically when creating a domain',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'nismachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>. Your NIS master (not slave master) machines, one per line. This enables export of `/etc/global/passwd\' and `/etc/global/shadow\'.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'passwordmin',
- 'section' => 'password',
- 'description' => 'Minimum password length (default 6)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'passwordmax',
- 'section' => 'password',
- 'description' => 'Maximum password length (default 8) (don\'t set this over 12 if you need to import or export crypt() passwords)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'password-noampersand',
- 'section' => 'password',
- 'description' => 'Disallow ampersands in passwords',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'password-noexclamation',
- 'section' => 'password',
- 'description' => 'Disallow exclamations in passwords (Not setting this could break old text Livingston or Cistron Radius servers)',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'qmailmachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add <i>qmail</i> and <i>shellcommands</i> <a href="../browse/part_export.cgi">exports</a> instead. This option used to export `/var/qmail/control/virtualdomains\', `/var/qmail/control/recipientmap\', and `/var/qmail/control/rcpthosts\'. Setting this option (even if empty) also turns on user `.qmail-extension\' file maintenance in conjunction with the <b>shellmachine</b> option.',
- 'type' => [qw( checkbox textarea )],
- },
-
- {
- 'key' => 'radiusmachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to export to be: your RADIUS authentication machines, one per line. This enables export of `/etc/raddb/users\'.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'referraldefault',
- 'section' => 'UI',
- 'description' => 'Default referral, specified by refnum',
- 'type' => 'text',
- },
-
-# {
-# 'key' => 'registries',
-# 'section' => 'required',
-# 'description' => 'Directory which contains domain registry information. Each registry is a directory.',
-# },
-
- {
- 'key' => 'report_template',
- 'section' => 'deprecated',
- 'description' => 'Deprecated template file for reports.',
- 'type' => 'textarea',
- },
-
-
- {
- 'key' => 'maxsearchrecordsperpage',
- 'section' => 'UI',
- 'description' => 'If set, number of search records to return per page.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'sendmailconfigpath',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>sendmail</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be sendmail configuration file path. Defaults to `/etc\'. Many newer distributions use `/etc/mail\'.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'sendmailmachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>sendmail</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be sendmail machines, one per line. This enables export of `/etc/virtusertable\' and `/etc/sendmail.cw\'.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'sendmailrestart',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>sendmail</i> <a href="../browse/part_export.cgi">export</a> instead. Used to define the command which is run on sendmail machines after files are copied.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'session-start',
- 'section' => 'session',
- 'description' => 'If defined, the command which is executed on the Freeside machine when a session begins. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'session-stop',
- 'section' => 'session',
- 'description' => 'If defined, the command which is executed on the Freeside machine when a session ends. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'shellmachine',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain a single machine with user home directories mounted. This enables home directory creation, renaming and archiving/deletion. In conjunction with `qmailmachines\', it also enables `.qmail-extension\' file maintenance.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'shellmachine-useradd',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain command(s) to run on shellmachine when an account is created. If the <b>shellmachine</b> option is set but this option is not, <code>useradd -d $dir -m -s $shell -u $uid $username</code> is the default. If this option is set but empty, <code>cp -pr /etc/skel $dir; chown -R $uid.$gid $dir</code> is the default instead. Otherwise the value is evaluated as a double-quoted perl string, with the following variables available: <code>$username</code>, <code>$uid</code>, <code>$gid</code>, <code>$dir</code>, and <code>$shell</code>.',
- 'type' => [qw( checkbox text )],
- },
-
- {
- 'key' => 'shellmachine-userdel',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain command(s) to run on shellmachine when an account is deleted. If the <b>shellmachine</b> option is set but this option is not, <code>userdel $username</code> is the default. If this option is set but empty, <code>rm -rf $dir</code> is the default instead. Otherwise the value is evaluated as a double-quoted perl string, with the following variables available: <code>$username</code> and <code>$dir</code>.',
- 'type' => [qw( checkbox text )],
- },
-
- {
- 'key' => 'shellmachine-usermod',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain command(s) to run on shellmachine when an account is modified. If the <b>shellmachine</b> option is set but this option is empty, <code>[ -d $old_dir ] &amp;&amp; mv $old_dir $new_dir || ( chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; find . -depth -print | cpio -pdm $new_dir; chmod u-t $new_dir; chown -R $uid.$gid $new_dir; rm -rf $old_dir )</code> is the default. Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$old_dir</code>, <code>$new_dir</code>, <code>$uid</code> and <code>$gid</code>.',
- #'type' => [qw( checkbox text )],
- 'type' => 'text',
- },
-
- {
- 'key' => 'shellmachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>sysvshell</i> <a href="../browse/part_export.cgi">export</a> instead. Your Linux and System V flavored shell (and mail) machines, one per line. This enables export of `/etc/passwd\' and `/etc/shadow\' files.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'shells',
- 'section' => 'required',
- 'description' => 'Legal shells (think /etc/shells). You probably want to `cut -d: -f7 /etc/passwd | sort | uniq\' initially so that importing doesn\'t fail with `Illegal shell\' errors, then remove any special entries afterwords. A blank line specifies that an empty shell is permitted.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'showpasswords',
- 'section' => 'UI',
- 'description' => 'Display unencrypted user passwords in the backend (employee) web interface',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'signupurl',
- 'section' => 'UI',
- 'description' => 'if you are using customer-to-customer referrals, and you enter the URL of your <a href="../docs/signup.html">signup server CGI</a>, the customer view screen will display a customized link to the signup server with the appropriate customer as referral',
- 'type' => 'text',
- },
-
- {
- 'key' => 'smtpmachine',
- 'section' => 'required',
- 'description' => 'SMTP relay for Freeside\'s outgoing mail',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soadefaultttl',
- 'section' => 'BIND',
- 'description' => 'SOA default TTL for new domains.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soaemail',
- 'section' => 'BIND',
- 'description' => 'SOA email for new domains, in BIND form (`.\' instead of `@\'), with trailing `.\'',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soaexpire',
- 'section' => 'BIND',
- 'description' => 'SOA expire for new domains',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soamachine',
- 'section' => 'BIND',
- 'description' => 'SOA machine for new domains, with trailing `.\'',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soarefresh',
- 'section' => 'BIND',
- 'description' => 'SOA refresh for new domains',
- 'type' => 'text',
- },
-
- {
- 'key' => 'soaretry',
- 'section' => 'BIND',
- 'description' => 'SOA retry for new domains',
- 'type' => 'text',
- },
-
- {
- 'key' => 'statedefault',
- 'section' => 'UI',
- 'description' => 'Default state or province (if not supplied, the default is `CA\')',
- 'type' => 'text',
- },
-
- {
- 'key' => 'radiusprepend',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, real-time text radius now edits an existing file in place - just (turn off freeside-queued and) edit your RADIUS users file directly. The contents used to be be prepended to the top of the RADIUS users file (text exports only).',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'textradiusprepend',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, use RADIUS check attributes instead. The contents used to be prepended to the first line of a user\'s RADIUS entry in text exports.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'unsuspendauto',
- 'section' => 'billing',
- 'description' => 'Enables the automatic unsuspension of suspended packages when a customer\'s balance due changes from positive to zero or negative as the result of a payment or credit',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'unsuspend-always_adjust_next_bill_date',
- 'section' => 'billing',
- 'description' => 'Global override that causes unsuspensions to always adjust the next bill date under any circumstances. This is now controlled on a per-package bases - probably best not to use this option unless you are a legacy installation that requires this behaviour.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'usernamemin',
- 'section' => 'username',
- 'description' => 'Minimum username length (default 2)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'usernamemax',
- 'section' => 'username',
- 'description' => 'Maximum username length',
- 'type' => 'text',
- },
-
- {
- 'key' => 'username-ampersand',
- 'section' => 'username',
- 'description' => 'Allow the ampersand character (&amp;) in usernames. Be careful when using this option in conjunction with <a href="../browse/part_export.cgi">exports</a> which execute shell commands, as the ampersand will be interpreted by the shell if not quoted.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-letter',
- 'section' => 'username',
- 'description' => 'Usernames must contain at least one letter',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-letterfirst',
- 'section' => 'username',
- 'description' => 'Usernames must start with a letter',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-noperiod',
- 'section' => 'username',
- 'description' => 'Disallow periods in usernames',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-nounderscore',
- 'section' => 'username',
- 'description' => 'Disallow underscores in usernames',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-nodash',
- 'section' => 'username',
- 'description' => 'Disallow dashes in usernames',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-uppercase',
- 'section' => 'username',
- 'description' => 'Allow uppercase characters in usernames',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username-percent',
- 'section' => 'username',
- 'description' => 'Allow the percent character (%) in usernames.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'username_policy',
- 'section' => 'deprecated',
- 'description' => 'This file controls the mechanism for preventing duplicate usernames in passwd/radius files exported from svc_accts. This should be one of \'prepend domsvc\' \'append domsvc\' \'append domain\' or \'append @domain\'',
- 'type' => 'select',
- 'select_enum' => [ 'prepend domsvc', 'append domsvc', 'append domain', 'append @domain' ],
- #'type' => 'text',
- },
-
- {
- 'key' => 'vpopmailmachines',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>vpopmail</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain your vpopmail pop toasters, one per line. Each line is of the form "machinename vpopdir vpopuid vpopgid". For example: <code>poptoaster.domain.tld /home/vpopmail 508 508</code> Note: vpopuid and vpopgid are values taken from the vpopmail machine\'s /etc/passwd',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'vpopmailrestart',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, add a <i>vpopmail</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to define the shell commands to run on vpopmail machines after files are copied. An example can be found in eg/vpopmailrestart of the source distribution.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'safe-part_pkg',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, obsolete. Used to validate package definition setup and recur expressions against a preset list. Useful for webdemos, annoying to powerusers.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'safe-part_bill_event',
- 'section' => 'UI',
- 'description' => 'Validates invoice event expressions against a preset list. Useful for webdemos, annoying to powerusers.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'show_ss',
- 'section' => 'UI',
- 'description' => 'Turns on display/collection of SS# in the web interface.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'show_stateid',
- 'section' => 'UI',
- 'description' => "Turns on display/collection of driver's license/state issued id numbers in the web interface. Sometimes required by electronic check (ACH) processors.",
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'show_bankstate',
- 'section' => 'UI',
- 'description' => "Turns on display/collection of state for bank accounts in the web interface. Sometimes required by electronic check (ACH) processors.",
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'agent_defaultpkg',
- 'section' => 'UI',
- 'description' => 'Setting this option will cause new packages to be available to all agent types by default.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'legacy_link',
- 'section' => 'UI',
- 'description' => 'Display options in the web interface to link legacy pre-Freeside services.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'legacy_link-steal',
- 'section' => 'UI',
- 'description' => 'Allow "stealing" an already-audited service from one customer (or package) to another using the link function.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'queue_dangerous_controls',
- 'section' => 'UI',
- 'description' => 'Enable queue modification controls on account pages and for new jobs. Unless you are a developer working on new export code, you should probably leave this off to avoid causing provisioning problems.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'security_phrase',
- 'section' => 'password',
- 'description' => 'Enable the tracking of a "security phrase" with each account. Not recommended, as it is vulnerable to social engineering.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'locale',
- 'section' => 'UI',
- 'description' => 'Message locale',
- 'type' => 'select',
- 'select_enum' => [ qw(en_US) ],
- },
-
- {
- 'key' => 'selfservice_server-quiet',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, the self-service server no longer sends superfluous decline and cancel emails. Used to disable decline and cancel emails generated by transactions initiated by the selfservice server.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'signup_server-quiet',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, the signup server is now part of the self-service server and no longer sends superfluous decline and cancel emails. Used to disable decline and cancel emails generated by transactions initiated by the signup server. Does not disable welcome emails.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'signup_server-payby',
- 'section' => '',
- 'description' => 'Acceptable payment types for the signup server',
- 'type' => 'selectmultiple',
- 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY BILL COMP) ],
- },
-
- {
- 'key' => 'signup_server-email',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, this feature is no longer available. See the ***fill me in*** report instead. Used to contain a comma-separated list of email addresses to receive notification of signups via the signup server.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'signup_server-default_agentnum',
- 'section' => '',
- 'description' => 'Default agent for the signup server',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::agent;
- map { $_->agentnum => $_->agent }
- FS::Record::qsearch('agent', { disabled=>'' } );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::agent;
- my $agent = FS::Record::qsearchs(
- 'agent', { 'agentnum'=>shift }
- );
- $agent ? $agent->agent : '';
- },
- },
-
- {
- 'key' => 'signup_server-default_refnum',
- 'section' => '',
- 'description' => 'Default advertising source for the signup server',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::part_referral;
- map { $_->refnum => $_->referral }
- FS::Record::qsearch( 'part_referral',
- { 'disabled' => '' }
- );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::part_referral;
- my $part_referral = FS::Record::qsearchs(
- 'part_referral', { 'refnum'=>shift } );
- $part_referral ? $part_referral->referral : '';
- },
- },
-
- {
- 'key' => 'signup_server-default_pkgpart',
- 'section' => '',
- 'description' => 'Default pakcage for the signup server',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::part_pkg;
- map { $_->pkgpart => $_->pkg.' - '.$_->comment }
- FS::Record::qsearch( 'part_pkg',
- { 'disabled' => ''}
- );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::part_pkg;
- my $part_pkg = FS::Record::qsearchs(
- 'part_pkg', { 'pkgpart'=>shift }
- );
- $part_pkg
- ? $part_pkg->pkg.' - '.$part_pkg->comment
- : '';
- },
- },
-
- {
- 'key' => 'show-msgcat-codes',
- 'section' => 'UI',
- 'description' => 'Show msgcat codes in error messages. Turn this option on before reporting errors to the mailing list.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'signup_server-realtime',
- 'section' => '',
- 'description' => 'Run billing for signup server signups immediately, and do not provision accounts which subsequently have a balance.',
- 'type' => 'checkbox',
- },
- {
- 'key' => 'signup_server-classnum2',
- 'section' => '',
- 'description' => 'Package Class for first optional purchase',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::pkg_class;
- map { $_->classnum => $_->classname }
- FS::Record::qsearch('pkg_class', {} );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::pkg_class;
- my $pkg_class = FS::Record::qsearchs(
- 'pkg_class', { 'classnum'=>shift }
- );
- $pkg_class ? $pkg_class->classname : '';
- },
- },
-
- {
- 'key' => 'signup_server-classnum3',
- 'section' => '',
- 'description' => 'Package Class for second optional purchase',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::pkg_class;
- map { $_->classnum => $_->classname }
- FS::Record::qsearch('pkg_class', {} );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::pkg_class;
- my $pkg_class = FS::Record::qsearchs(
- 'pkg_class', { 'classnum'=>shift }
- );
- $pkg_class ? $pkg_class->classname : '';
- },
- },
-
- {
- 'key' => 'backend-realtime',
- 'section' => '',
- 'description' => 'Run billing for backend signups immediately.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'declinetemplate',
- 'section' => 'billing',
- 'description' => 'Template file for credit card decline emails.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'emaildecline',
- 'section' => 'billing',
- 'description' => 'Enable emailing of credit card decline notices.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'emaildecline-exclude',
- 'section' => 'billing',
- 'description' => 'List of error messages that should not trigger email decline notices, one per line.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cancelmessage',
- 'section' => 'billing',
- 'description' => 'Template file for cancellation emails.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cancelsubject',
- 'section' => 'billing',
- 'description' => 'Subject line for cancellation emails.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'emailcancel',
- 'section' => 'billing',
- 'description' => 'Enable emailing of cancellation notices.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'require_cardname',
- 'section' => 'billing',
- 'description' => 'Require an "Exact name on card" to be entered explicitly; don\'t default to using the first and last name.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'enable_taxclasses',
- 'section' => 'billing',
- 'description' => 'Enable per-package tax classes',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'require_taxclasses',
- 'section' => 'billing',
- 'description' => 'Require a taxclass to be entered for every package',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'welcome_email',
- 'section' => '',
- 'description' => 'Template file for welcome email. Welcome emails are sent to the customer email invoice destination(s) each time a svc_acct record is created. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code></ul>',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'welcome_email-from',
- 'section' => '',
- 'description' => 'From: address header for welcome email',
- 'type' => 'text',
- },
-
- {
- 'key' => 'welcome_email-subject',
- 'section' => '',
- 'description' => 'Subject: header for welcome email',
- 'type' => 'text',
- },
-
- {
- 'key' => 'welcome_email-mimetype',
- 'section' => '',
- 'description' => 'MIME type for welcome email',
- 'type' => 'select',
- 'select_enum' => [ 'text/plain', 'text/html' ],
- },
-
- {
- 'key' => 'welcome_letter',
- 'section' => '',
- 'description' => 'Optional LaTex template file for a printed welcome letter. A welcome letter is printed the first time a cust_pkg record is created. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation and the billing documentation for details on the template substitution language. A variable exists for each fieldname in the customer record (<code>$first, $last, etc</code>). The following additional variables are available<ul><li><code>$payby</code> - a friendler represenation of the field<li><code>$payinfo</code> - the masked payment information<li><code>$expdate</code> - the time at which the payment method expires (a UNIX timestamp)<li><code>$returnaddress</code> - the invoice return address for this customer\'s agent</ul>',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'warning_email',
- 'section' => '',
- 'description' => 'Template file for warning email. Warning emails are sent to the customer email invoice destination(s) each time a svc_acct record has its usage drop below a threshold or 0. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code> <li><code>$column</code> <li><code>$amount</code> <li><code>$threshold</code></ul>',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'warning_email-from',
- 'section' => '',
- 'description' => 'From: address header for warning email',
- 'type' => 'text',
- },
-
- {
- 'key' => 'warning_email-cc',
- 'section' => '',
- 'description' => 'Additional recipient(s) (comma separated) for warning email when remaining usage reaches zero.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'warning_email-subject',
- 'section' => '',
- 'description' => 'Subject: header for warning email',
- 'type' => 'text',
- },
-
- {
- 'key' => 'warning_email-mimetype',
- 'section' => '',
- 'description' => 'MIME type for warning email',
- 'type' => 'select',
- 'select_enum' => [ 'text/plain', 'text/html' ],
- },
-
- {
- 'key' => 'payby',
- 'section' => 'billing',
- 'description' => 'Available payment types.',
- 'type' => 'selectmultiple',
- 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD COMP) ],
- },
-
- {
- 'key' => 'payby-default',
- 'section' => 'UI',
- 'description' => 'Default payment type. HIDE disables display of billing information and sets customers to BILL.',
- 'type' => 'select',
- 'select_enum' => [ '', qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD COMP HIDE) ],
- },
-
- {
- 'key' => 'paymentforcedtobatch',
- 'section' => 'UI',
- 'description' => 'Causes per customer payment entry to be forced to a batch processor rather than performed realtime.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-notes',
- 'section' => 'UI',
- 'description' => 'Extra HTML to be displayed on the Account View screen.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'radius-password',
- 'section' => '',
- 'description' => 'RADIUS attribute for plain-text passwords.',
- 'type' => 'select',
- 'select_enum' => [ 'Password', 'User-Password' ],
- },
-
- {
- 'key' => 'radius-ip',
- 'section' => '',
- 'description' => 'RADIUS attribute for IP addresses.',
- 'type' => 'select',
- 'select_enum' => [ 'Framed-IP-Address', 'Framed-Address' ],
- },
-
- {
- 'key' => 'svc_acct-alldomains',
- 'section' => '',
- 'description' => 'Allow accounts to select any domain in the database. Normally accounts can only select from the domain set in the service definition and those purchased by the customer.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'dump-scpdest',
- 'section' => '',
- 'description' => 'destination for scp database dumps: user@host:/path',
- 'type' => 'text',
- },
-
- {
- 'key' => 'dump-pgpid',
- 'section' => '',
- 'description' => "Optional PGP public key user or key id for database dumps. The public key should exist on the freeside user's public keyring, and the gpg binary and GnuPG perl module should be installed.",
- 'type' => 'text',
- },
-
- {
- 'key' => 'users-allow_comp',
- 'section' => 'deprecated',
- 'description' => '<b>DEPRECATED</b>, enable the <i>Complimentary customer</i> access right instead. Was: Usernames (Freeside users, created with <a href="../docs/man/bin/freeside-adduser.html">freeside-adduser</a>) which can create complimentary customers, one per line. If no usernames are entered, all users can create complimentary accounts.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cvv-save',
- 'section' => 'billing',
- 'description' => 'Save CVV2 information after the initial transaction for the selected credit card types. Enabling this option may be in violation of your merchant agreement(s), so please check them carefully before enabling this option for any credit card types.',
- 'type' => 'selectmultiple',
- 'select_enum' => \@card_types,
- },
-
- {
- 'key' => 'allow_negative_charges',
- 'section' => 'billing',
- 'description' => 'Allow negative charges. Normally not used unless importing data from a legacy system that requires this.',
- 'type' => 'checkbox',
- },
- {
- 'key' => 'auto_unset_catchall',
- 'section' => '',
- 'description' => 'When canceling a svc_acct that is the email catchall for one or more svc_domains, automatically set their catchall fields to null. If this option is not set, the attempt will simply fail.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'system_usernames',
- 'section' => 'username',
- 'description' => 'A list of system usernames that cannot be edited or removed, one per line. Use a bare username to prohibit modification/deletion of the username in any domain, or username@domain to prohibit modification/deletetion of a specific username and domain.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cust_pkg-change_svcpart',
- 'section' => '',
- 'description' => "When changing packages, move services even if svcparts don't match between old and new pacakge definitions.",
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'disable_autoreverse',
- 'section' => 'BIND',
- 'description' => 'Disable automatic synchronization of reverse-ARPA entries.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_www-enable_subdomains',
- 'section' => '',
- 'description' => 'Enable selection of specific subdomains for virtual host creation.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_www-usersvc_svcpart',
- 'section' => '',
- 'description' => 'Allowable service definition svcparts for virtual hosts, one per line.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'selfservice_server-primary_only',
- 'section' => '',
- 'description' => 'Only allow primary accounts to access self-service functionality.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'card_refund-days',
- 'section' => 'billing',
- 'description' => 'After a payment, the number of days a refund link will be available for that payment. Defaults to 120.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'agent-showpasswords',
- 'section' => '',
- 'description' => 'Display unencrypted user passwords in the agent (reseller) interface',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'global_unique-username',
- 'section' => 'username',
- 'description' => 'Global username uniqueness control: none (usual setting - check uniqueness per exports), username (all usernames are globally unique, regardless of domain or exports), or username@domain (all username@domain pairs are globally unique, regardless of exports). disabled turns off duplicate checking completely and is STRONGLY NOT RECOMMENDED unless you REALLY need to turn this off.',
- 'type' => 'select',
- 'select_enum' => [ 'none', 'username', 'username@domain', 'disabled' ],
- },
-
- {
- 'key' => 'svc_external-skip_manual',
- 'section' => 'UI',
- 'description' => 'When provisioning svc_external services, skip manual entry of id and title fields in the UI. Usually used in conjunction with an export that populates these fields (i.e. artera_turbo).',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_external-display_type',
- 'section' => 'UI',
- 'description' => 'Select a specific svc_external type to enable some UI changes specific to that type (i.e. artera_turbo).',
- 'type' => 'select',
- 'select_enum' => [ 'generic', 'artera_turbo', ],
- },
-
- {
- 'key' => 'ticket_system',
- 'section' => '',
- 'description' => 'Ticketing system integration. <b>RT_Internal</b> uses the built-in RT ticketing system (see the <a href="../docs/install-rt">integrated ticketing installation instructions</a>). <b>RT_External</b> accesses an external RT installation in a separate database (local or remote).',
- 'type' => 'select',
- #'select_enum' => [ '', qw(RT_Internal RT_Libs RT_External) ],
- 'select_enum' => [ '', qw(RT_Internal RT_External) ],
- },
-
- {
- 'key' => 'ticket_system-default_queueid',
- 'section' => '',
- 'description' => 'Default queue used when creating new customer tickets.',
- 'type' => 'select-sub',
- 'options_sub' => sub {
- my $conf = new FS::Conf;
- if ( $conf->config('ticket_system') ) {
- eval "use FS::TicketSystem;";
- die $@ if $@;
- FS::TicketSystem->queues();
- } else {
- ();
- }
- },
- 'option_sub' => sub {
- my $conf = new FS::Conf;
- if ( $conf->config('ticket_system') ) {
- eval "use FS::TicketSystem;";
- die $@ if $@;
- FS::TicketSystem->queue(shift);
- } else {
- '';
- }
- },
- },
-
- {
- 'key' => 'ticket_system-custom_priority_field',
- 'section' => '',
- 'description' => 'Custom field from the ticketing system to use as a custom priority classification.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'ticket_system-custom_priority_field-values',
- 'section' => '',
- 'description' => 'Values for the custom field from the ticketing system to break down and sort customer ticket lists.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'ticket_system-custom_priority_field_queue',
- 'section' => '',
- 'description' => 'Ticketing system queue in which the custom field specified in ticket_system-custom_priority_field is located.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'ticket_system-rt_external_datasrc',
- 'section' => '',
- 'description' => 'With external RT integration, the DBI data source for the external RT installation, for example, <code>DBI:Pg:user=rt_user;password=rt_word;host=rt.example.com;dbname=rt</code>',
- 'type' => 'text',
-
- },
-
- {
- 'key' => 'ticket_system-rt_external_url',
- 'section' => '',
- 'description' => 'With external RT integration, the URL for the external RT installation, for example, <code>https://rt.example.com/rt</code>',
- 'type' => 'text',
- },
-
- {
- 'key' => 'company_name',
- 'section' => 'required',
- 'description' => 'Your company name',
- 'type' => 'text',
- },
-
- {
- 'key' => 'echeck-void',
- 'section' => 'deprecated',
- 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable local-only voiding of echeck payments in addition to refunds against the payment gateway',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cc-void',
- 'section' => 'deprecated',
- 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable local-only voiding of credit card payments in addition to refunds against the payment gateway',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'unvoid',
- 'section' => 'deprecated',
- 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable unvoiding of voided payments',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'address2-search',
- 'section' => 'UI',
- 'description' => 'Enable a "Unit" search box which searches the second address field',
- 'type' => 'checkbox',
- },
-
- { 'key' => 'referral_credit',
- 'section' => 'billing',
- 'description' => "Enables one-time referral credits in the amount of one month <i>referred</i> customer's recurring fee (irregardless of frequency).",
- 'type' => 'checkbox',
- },
-
- { 'key' => 'selfservice_server-cache_module',
- 'section' => '',
- 'description' => 'Module used to store self-service session information. All modules handle any number of self-service servers. Cache::SharedMemoryCache is appropriate for a single database / single Freeside server. Cache::FileCache is useful for multiple databases on a single server, or when IPC::ShareLite is not available (i.e. FreeBSD).', # _Database stores session information in the database and is appropriate for multiple Freeside servers, but may be slower.',
- 'type' => 'select',
- 'select_enum' => [ 'Cache::SharedMemoryCache', 'Cache::FileCache', ], # '_Database' ],
- },
-
- {
- 'key' => 'hylafax',
- 'section' => '',
- 'description' => 'Options for a HylaFAX server to enable the FAX invoice destination. They should be in the form of a space separated list of arguments to the Fax::Hylafax::Client::sendfax subroutine. You probably shouldn\'t override things like \'docfile\'. *Note* Only supported when using typeset invoices (see the invoice_latex configuration option).',
- 'type' => [qw( checkbox textarea )],
- },
-
- {
- 'key' => 'svc_acct-usage_suspend',
- 'section' => 'billing',
- 'description' => 'Suspends the package an account belongs to when svc_acct.seconds or a bytecount is decremented to 0 or below (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-usage_unsuspend',
- 'section' => 'billing',
- 'description' => 'Unuspends the package an account belongs to when svc_acct.seconds or a bytecount is incremented from 0 or below to a positive value (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-usage_threshold',
- 'section' => 'billing',
- 'description' => 'The threshold (expressed as percentage) of acct.seconds or acct.up|down|totalbytes at which a warning message is sent to a service holder. Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd. Defaults to 80.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'cust-fields',
- 'section' => 'UI',
- 'description' => 'Which customer fields to display on reports by default',
- 'type' => 'select',
- 'select_hash' => [ FS::ConfDefaults->cust_fields_avail() ],
- },
-
- {
- 'key' => 'cust_pkg-display_times',
- 'section' => 'UI',
- 'description' => 'Display full timestamps (not just dates) for customer packages. Useful if you are doing real-time things like hourly prepaid.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-edit_uid',
- 'section' => 'shell',
- 'description' => 'Allow UID editing.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_acct-edit_gid',
- 'section' => 'shell',
- 'description' => 'Allow GID editing.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'zone-underscore',
- 'section' => 'BIND',
- 'description' => 'Allow underscores in zone names. As underscores are illegal characters in zone names, this option is not recommended.',
- 'type' => 'checkbox',
- },
-
- #these should become per-user...
- {
- 'key' => 'vonage-username',
- 'section' => '',
- 'description' => 'Vonage Click2Call username (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
- 'type' => 'text',
- },
- {
- 'key' => 'vonage-password',
- 'section' => '',
- 'description' => 'Vonage Click2Call username (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
- 'type' => 'text',
- },
- {
- 'key' => 'vonage-fromnumber',
- 'section' => '',
- 'description' => 'Vonage Click2Call number (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
- 'type' => 'text',
- },
-
- {
- 'key' => 'echeck-nonus',
- 'section' => 'billing',
- 'description' => 'Disable ABA-format account checking for Electronic Check payment info',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'voip-cust_cdr_spools',
- 'section' => '',
- 'description' => 'Enable the per-customer option for individual CDR spools.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'svc_forward-arbitrary_dst',
- 'section' => '',
- 'description' => "Allow forwards to point to arbitrary strings that don't necessarily look like email addresses. Only used when using forwards for weird, non-email things.",
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'tax-ship_address',
- 'section' => 'billing',
- 'description' => 'By default, tax calculations are done based on the billing address. Enable this switch to calculate tax based on the shipping address instead. Note: Tax reports can take a long time when enabled.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'batch-enable',
- 'section' => 'billing',
- 'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'batch-default_format',
- 'section' => 'billing',
- 'description' => 'Default format for batches.',
- 'type' => 'select',
- 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch',
- 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP',
- 'ach-spiritone',
- ]
- },
-
- {
- 'key' => 'batch-fixed_format-CARD',
- 'section' => 'billing',
- 'description' => 'Fixed (unchangeable) format for credit card batches.',
- 'type' => 'select',
- 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ,
- 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP' ]
- },
-
- {
- 'key' => 'batch-fixed_format-CHEK',
- 'section' => 'billing',
- 'description' => 'Fixed (unchangeable) format for electronic check batches.',
- 'type' => 'select',
- 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP',
- 'ach-spiritone',
- ]
- },
-
- {
- 'key' => 'batch-increment_expiration',
- 'section' => 'billing',
- 'description' => 'Increment expiration date years in batches until cards are current. Make sure this is acceptable to your batching provider before enabling.',
- 'type' => 'checkbox'
- },
-
- {
- 'key' => 'batchconfig-BoM',
- 'section' => 'billing',
- 'description' => 'Configuration for Bank of Montreal batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'batchconfig-PAP',
- 'section' => 'billing',
- 'description' => 'Configuration for PAP batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'batchconfig-csv-chase_canada-E-xactBatch',
- 'section' => 'billing',
- 'description' => 'Gateway ID for Chase Canada E-xact batching',
- 'type' => 'text',
- },
-
- {
- 'key' => 'payment_history-years',
- 'section' => 'UI',
- 'description' => 'Number of years of payment history to show by default. Currently defaults to 2.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'cust_main-use_comments',
- 'section' => 'UI',
- 'description' => 'Display free form comments on the customer edit screen. Useful as a scratch pad.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cust_main-disable_notes',
- 'section' => 'UI',
- 'description' => 'Disable new style customer notes - timestamped and user identified customer notes. Useful in tracking who did what.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cust_main_note-display_times',
- 'section' => 'UI',
- 'description' => 'Display full timestamps (not just dates) for customer notes.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cust_main-ticket_statuses',
- 'section' => 'UI',
- 'description' => 'Show tickets with these statuses on the customer view page.',
- 'type' => 'selectmultiple',
- 'select_enum' => [qw( new open stalled resolved rejected deleted )],
- },
-
- {
- 'key' => 'cust_main-max_tickets',
- 'section' => 'UI',
- 'description' => 'Maximum number of tickets to show on the customer view page.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'cust_main-skeleton_tables',
- 'section' => '',
- 'description' => 'Tables which will have skeleton records inserted into them for each customer. Syntax for specifying tables is unfortunately a tricky perl data structure for now.',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'cust_main-skeleton_custnum',
- 'section' => '',
- 'description' => 'Customer number specifying the source data to copy into skeleton tables for new customers.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'cust_main-enable_birthdate',
- 'section' => 'UI',
- 'descritpion' => 'Enable tracking of a birth date with each customer record',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'support-key',
- 'section' => '',
- 'description' => 'A support key enables access to commercial services delivered over the network, such as the payroll module, access to the internal ticket system, priority support and optional backups.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'card-types',
- 'section' => 'billing',
- 'description' => 'Select one or more card types to enable only those card types. If no card types are selected, all card types are available.',
- 'type' => 'selectmultiple',
- 'select_enum' => \@card_types,
- },
-
- {
- 'key' => 'dashboard-toplist',
- 'section' => 'UI',
- 'description' => 'List of items to display on the top of the front page',
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'impending_recur_template',
- 'section' => 'billing',
- 'description' => 'Template file for alerts about looming first time recurrant billing. See the <a href="http://search.cpan.org/~mjd/Text-Template.pm">Text::Template</a> documentation for details on the template substitition language. Also see packages with a <a href="../browse/part_pkg.cgi">flat price plan</a> The following variables are available<ul><li><code>$packages</code> allowing <code>$packages->[0]</code> thru <code>$packages->[n]</code> <li><code>$package</code> the first package, same as <code>$packages->[0]</code> <li><code>$recurdates</code> allowing <code>$recurdates->[0]</code> thru <code>$recurdates->[n]</code> <li><code>$recurdate</code> the first recurdate, same as <code>$recurdate->[0]</code> <li><code>$first</code> <li><code>$last</code></ul>',
-# <li><code>$payby</code> <li><code>$expdate</code> most likely only confuse
- 'type' => 'textarea',
- },
-
- {
- 'key' => 'disable_setup_suspended_pkgs',
- 'section' => 'billing',
- 'description' => 'Disables charging of setup fees for suspended packages.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'password-generated-allcaps',
- 'section' => 'password',
- 'description' => 'Causes passwords automatically generated to consist entirely of capital letters',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'datavolume-forcemegabytes',
- 'section' => 'UI',
- 'description' => 'All data volumes are expressed in megabytes',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'datavolume-significantdigits',
- 'section' => 'UI',
- 'description' => 'number of significant digits to use to represent data volumes',
- 'type' => 'text',
- },
-
- {
- 'key' => 'disable_void_after',
- 'section' => 'billing',
- 'description' => 'Number of seconds after which freeside won\'t attempt to VOID a payment first when performing a refund.',
- 'type' => 'text',
- },
-
- {
- 'key' => 'disable_line_item_date_ranges',
- 'section' => 'billing',
- 'description' => 'Prevent freeside from automatically generating date ranges on invoice line items.',
- 'type' => 'checkbox',
- },
-
- {
- 'key' => 'cancel_credit_type',
- 'section' => 'billing',
- 'description' => 'The group to use for new, automatically generated credit reasons resulting from cancellation.',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::reason_type;
- map { $_->typenum => $_->type }
- FS::Record::qsearch('reason_type', { class=>'R' } );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::reason_type;
- my $reason_type = FS::Record::qsearchs(
- 'reason_type', { 'typenum' => shift }
- );
- $reason_type ? $reason_type->type : '';
- },
- },
-
- {
- 'key' => 'referral_credit_type',
- 'section' => 'billing',
- 'description' => 'The group to use for new, automatically generated credit reasons resulting from referrals.',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::reason_type;
- map { $_->typenum => $_->type }
- FS::Record::qsearch('reason_type', { class=>'R' } );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::reason_type;
- my $reason_type = FS::Record::qsearchs(
- 'reason_type', { 'typenum' => shift }
- );
- $reason_type ? $reason_type->type : '';
- },
- },
-
- {
- 'key' => 'signup_credit_type',
- 'section' => 'billing',
- 'description' => 'The group to use for new, automatically generated credit reasons resulting from signup and self-service declines.',
- 'type' => 'select-sub',
- 'options_sub' => sub { require FS::Record;
- require FS::reason_type;
- map { $_->typenum => $_->type }
- FS::Record::qsearch('reason_type', { class=>'R' } );
- },
- 'option_sub' => sub { require FS::Record;
- require FS::reason_type;
- my $reason_type = FS::Record::qsearchs(
- 'reason_type', { 'typenum' => shift }
- );
- $reason_type ? $reason_type->type : '';
- },
- },
-
-);
-
-1;
-
diff --git a/FS/FS/Cron/backup.pm b/FS/FS/Cron/backup.pm
deleted file mode 100644
index 204069a..0000000
--- a/FS/FS/Cron/backup.pm
+++ /dev/null
@@ -1,43 +0,0 @@
-package FS::Cron::backup;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK );
-use Exporter;
-use FS::UID qw(driver_name datasrc);
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw( backup_scp );
-
-sub backup_scp {
- my $conf = new FS::Conf;
- my $dest = $conf->config('dump-scpdest');
- if ( $dest ) {
- datasrc =~ /dbname=([\w\.]+)$/ or die "unparsable datasrc ". datasrc;
- my $database = $1;
- eval "use Net::SCP qw(scp);";
- die $@ if $@;
- if ( driver_name eq 'Pg' ) {
- system("pg_dump $database >/var/tmp/$database.sql")
- } else {
- die "database dumps not yet supported for ". driver_name;
- }
- if ( $conf->config('dump-pgpid') ) {
- eval 'use GnuPG;';
- die $@ if $@;
- my $gpg = new GnuPG;
- $gpg->encrypt( plaintext => "/var/tmp/$database.sql",
- output => "/var/tmp/$database.gpg",
- recipient => $conf->config('dump-pgpid'),
- );
- chmod 0600, '/var/tmp/$database.gpg';
- scp("/var/tmp/$database.gpg", $dest);
- unlink "/var/tmp/$database.gpg" or die $!;
- } else {
- chmod 0600, '/var/tmp/$database.sql';
- scp("/var/tmp/$database.sql", $dest);
- }
- unlink "/var/tmp/$database.sql" or die $!;
- }
-}
-
-1;
diff --git a/FS/FS/Cron/bill.pm b/FS/FS/Cron/bill.pm
deleted file mode 100644
index 7de2ff2..0000000
--- a/FS/FS/Cron/bill.pm
+++ /dev/null
@@ -1,150 +0,0 @@
-package FS::Cron::bill;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK );
-use Exporter;
-use Date::Parse;
-use FS::UID qw(dbh);
-use FS::Record qw(qsearchs);
-use FS::cust_main;
-use FS::part_event;
-use FS::part_event_condition;
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw ( bill );
-
-sub bill {
-
- my %opt = @_;
-
- my $check_freq = $opt{'check_freq'} || '1d';
-
- my $debug = 0;
- $debug = 1 if $opt{'v'};
- $debug = $opt{'l'} if $opt{'l'};
-
- $FS::cust_main::DEBUG = $debug;
- #$FS::cust_event::DEBUG = $opt{'l'} if $opt{'l'};
-
- my @search = ();
-
- push @search, "cust_main.payby = '". $opt{'p'}. "'"
- if $opt{'p'};
- push @search, "cust_main.agentnum = ". $opt{'a'}
- if $opt{'a'};
-
- if ( @ARGV ) {
- push @search, "( ".
- join(' OR ', map "cust_main.custnum = $_", @ARGV ).
- " )";
- }
-
- ###
- # generate where_pkg/where_event search clause
- ###
-
- #we're at now now (and later).
- my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T;
- $time += $opt{'y'} * 86400 if $opt{'y'};
-
- my $invoice_time = $opt{'n'} ? $^T : $time;
-
- # select * from cust_main where
- my $where_pkg = <<"END";
- 0 < ( select count(*) from cust_pkg
- where cust_main.custnum = cust_pkg.custnum
- and ( cancel is null or cancel = 0 )
- and ( setup is null or setup = 0
- or bill is null or bill <= $time
- or ( expire is not null and expire <= $^T )
- or ( adjourn is not null and adjourn <= $^T )
- )
- )
-END
-
- my $where_event = join(' OR ', map {
- my $eventtable = $_;
-
- my $join = FS::part_event_condition->join_conditions_sql( $eventtable );
- my $where = FS::part_event_condition->where_conditions_sql( $eventtable,
- 'time'=>$time,
- );
-
- my $are_part_event =
- "0 < ( SELECT COUNT(*) FROM part_event $join
- WHERE check_freq = '$check_freq'
- AND eventtable = '$eventtable'
- AND ( disabled = '' OR disabled IS NULL )
- AND $where
- )
- ";
-
- if ( $eventtable eq 'cust_main' ) {
- $are_part_event;
- } else {
- "0 < ( SELECT COUNT(*) FROM $eventtable
- WHERE cust_main.custnum = $eventtable.custnum
- AND $are_part_event
- )
- ";
- }
-
- } FS::part_event->eventtables);
-
- push @search, "( $where_pkg OR $where_event )";
-
- ###
- # get a list of custnums
- ###
-
- warn "searching for customers:\n". join("\n", @search). "\n"
- if $opt{'v'} || $opt{'l'};
-
- my $sth = dbh->prepare(
- "SELECT custnum FROM cust_main".
- " WHERE ". join(' AND ', @search)
- ) or die dbh->errstr;
-
- $sth->execute or die $sth->errstr;
-
- my @custnums = map { $_->[0] } @{ $sth->fetchall_arrayref };
-
- ###
- # for each custnum, queue or make one customer object and bill
- # (one at a time, to reduce memory footprint with large #s of customers)
- ###
-
- foreach my $custnum ( @custnums ) {
-
- if ( $opt{'m'} ) {
-
- #add job to queue that calls bill_and_collect with options
- my $queue = new FS::queue {
- 'job' => 'FS::cust_main::queued_bill',
- 'secure' => 'Y',
- };
- my $error = $queue->insert(
- 'custnum' => $custnum,
- 'time' => $time,
- 'invoice_time' => $invoice_time,
- 'check_freq' => $check_freq,
- 'resetup' => $opt{'s'} ? $opt{'s'} : 0,
- );
-
- } else {
-
- my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } );
-
- $cust_main->bill_and_collect(
- 'time' => $time,
- 'invoice_time' => $invoice_time,
- 'check_freq' => $check_freq,
- 'resetup' => $opt{'s'},
- 'debug' => $debug,
- );
-
- }
-
- }
-
-}
diff --git a/FS/FS/Cron/expire_user_pref.pm b/FS/FS/Cron/expire_user_pref.pm
deleted file mode 100644
index 3226927..0000000
--- a/FS/FS/Cron/expire_user_pref.pm
+++ /dev/null
@@ -1,20 +0,0 @@
-package FS::Cron::expire_user_pref;
-
-use vars qw( @ISA @EXPORT_OK);
-use Exporter;
-use FS::UID qw(dbh);
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw( expire_user_pref );
-
-sub expire_user_pref {
- my $sql = "DELETE FROM access_user_pref WHERE expiration IS NOT NULL".
- " AND expiration < ?";
- my $sth = dbh->prepare($sql) or die dbh->errstr;
- $sth->execute(time) or die $sth->errstr;
-
- dbh->commit or die dbh->errstr if $FS::UID::AutoCommit
-
-}
-
-1;
diff --git a/FS/FS/Cron/notify.pm b/FS/FS/Cron/notify.pm
deleted file mode 100644
index 23cf920..0000000
--- a/FS/FS/Cron/notify.pm
+++ /dev/null
@@ -1,149 +0,0 @@
-package FS::Cron::notify;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK $DEBUG );
-use Exporter;
-use FS::UID qw( dbh driver_name );
-use FS::Record qw(qsearch);
-use FS::cust_main;
-use FS::cust_pkg;
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw ( notify_flat_delay );
-$DEBUG = 0;
-
-sub notify_flat_delay {
-
- my %opt = @_;
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- $DEBUG = 1 if $opt{'v'};
-
- #we're at now now (and later).
- my($time) = $^T;
-
- my $integer = driver_name =~ /^mysql/ ? 'SIGNED' : 'INTEGER';
-
- # select * from cust_pkg where
- my $where_pkg = <<"END";
- where ( cancel is null or cancel = 0 )
- and ( bill > 0 )
- and
- 0 < ( select count(*) from part_pkg
- where cust_pkg.pkgpart = part_pkg.pkgpart
- and part_pkg.plan = 'flat_delayed'
- and 0 < ( select count(*) from part_pkg_option
- where part_pkg.pkgpart = part_pkg_option.pkgpart
- and part_pkg_option.optionname = 'recur_notify'
- and part_pkg_option.optionvalue > 0
- and 0 <= ( $time
- + CAST( part_pkg_option.optionvalue AS $integer )
- * 86400
- - cust_pkg.bill
- )
- and ( cust_pkg.expire is null
- or cust_pkg.expire > ( $time
- + CAST( part_pkg_option.optionvalue AS $integer )
- * 86400
- )
-END
-
-#/* and ( cust_pkg.adjourn is null
-# or cust_pkg.adjourn > $time
-#-- Should notify suspended ones + cast(part_pkg_option.optionvalue as $integer)
-# * 86400
-#*/
-
- $where_pkg .= <<"END";
- )
- )
- )
- and
- 0 = ( select count(*) from cust_pkg_option
- where cust_pkg.pkgnum = cust_pkg_option.pkgnum
- and cust_pkg_option.optionname = 'impending_recur_notification_sent'
- and cust_pkg_option.optionvalue = 1
- )
-END
-
- if ($opt{a}) {
- $where_pkg .= <<END;
- and 0 < ( select count(*) from cust_main
- where cust_pkg.custnum = cust_main.custnum
- and cust_main.agentnum = $opt{a}
- )
-END
- }
-
- my @cust_pkg;
- if ( @ARGV ) {
- $where_pkg .= "and ( " . join( "OR ", map { "custnum = $_" } @ARGV) . " )";
- }
-
- my $orderby = "order by custnum, bill";
-
- my $extra_sql = "$where_pkg $orderby";
-
- @cust_pkg = qsearch('cust_pkg', {}, '', $extra_sql );
-
- my @packages = ();
- my @recurdates = ();
- my @cust_pkgs = ();
- while ( scalar(@cust_pkg) ) {
- my $cust_main = $cust_pkg[0]->cust_main;
- my $custnum = $cust_pkg[0]->custnum;
- warn "working on $custnum" if $DEBUG;
- while (scalar(@cust_pkg)){
- last if ($cust_pkg[0]->custnum != $custnum);
- warn "storing information on " . $cust_pkg[0]->pkgnum if $DEBUG;
- push @packages, $cust_pkg[0]->part_pkg->pkg;
- push @recurdates, $cust_pkg[0]->bill;
- push @cust_pkgs, $cust_pkg[0];
- shift @cust_pkg;
- }
- my $error =
- $cust_main->notify( 'impending_recur_template',
- 'extra_fields' => { 'packages' => \@packages,
- 'recurdates' => \@recurdates,
- 'package' => $packages[0],
- 'recurdate' => $recurdates[0],
- },
- );
- warn "Error notifying, custnum ". $cust_main->custnum. ": $error" if $error;
-
- unless ($error) {
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- for (@cust_pkgs) {
- my %options = ($_->options, 'impending_recur_notification_sent' => 1 );
- $error = $_->replace( $_, options => \%options );
- if ($error){
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- die "Error updating package options for customer". $cust_main->custnum.
- ": $error" if $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- }
-
- @packages = ();
- @recurdates = ();
- @cust_pkgs = ();
-
- }
-
- dbh->commit or die dbh->errstr if $oldAutoCommit;
-
-}
-
-1;
diff --git a/FS/FS/Cron/vacuum.pm b/FS/FS/Cron/vacuum.pm
deleted file mode 100644
index 075572d..0000000
--- a/FS/FS/Cron/vacuum.pm
+++ /dev/null
@@ -1,23 +0,0 @@
-package FS::Cron::vacuum;
-
-use vars qw( @ISA @EXPORT_OK);
-use Exporter;
-use FS::UID qw(driver_name dbh);
-use FS::Schema qw(dbdef);
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw( vacuum );
-
-sub vacuum {
-
- if ( driver_name eq 'Pg' ) {
- dbh->{AutoCommit} = 1; #so we can vacuum
- foreach my $table ( dbdef->tables ) {
- my $sth = dbh->prepare("VACUUM ANALYZE $table") or die dbh->errstr;
- $sth->execute or die $sth->errstr;
- }
- }
-
-}
-
-1;
diff --git a/FS/FS/CurrentUser.pm b/FS/FS/CurrentUser.pm
deleted file mode 100644
index bcd337d..0000000
--- a/FS/FS/CurrentUser.pm
+++ /dev/null
@@ -1,67 +0,0 @@
-package FS::CurrentUser;
-
-use vars qw($CurrentUser $upgrade_hack);
-
-#not at compile-time, circular dependancey causes trouble
-#use FS::Record qw(qsearchs);
-#use FS::access_user;
-
-$upgrade_hack = 0;
-
-=head1 NAME
-
-FS::CurrentUser - Package representing the current user
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-=cut
-
-sub load_user {
- my( $class, $user ) = @_; #, $pass
-
- if ( $upgrade_hack ) {
- return $CurrentUser = new FS::CurrentUser::BootstrapUser;
- }
-
- #return "" if $user =~ /^fs_(queue|selfservice)$/;
-
- #not the best thing in the world...
- eval "use FS::Record qw(qsearchs);";
- die $@ if $@;
- eval "use FS::access_user;";
- die $@ if $@;
-
- $CurrentUser = qsearchs('access_user', {
- 'username' => $user,
- #'_password' =>
- 'disabled' => '',
- } );
-
- die "unknown user: $user" unless $CurrentUser; # or bad password
-
- $CurrentUser;
-}
-
-=head1 BUGS
-
-Creepy crawlies
-
-=head1 SEE ALSO
-
-=cut
-
-package FS::CurrentUser::BootstrapUser;
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
- bless ($self, $class);
-}
-
-sub AUTOLOAD { 1 };
-
-1;
-
diff --git a/FS/FS/Daemon.pm b/FS/FS/Daemon.pm
deleted file mode 100644
index 7e0d45c..0000000
--- a/FS/FS/Daemon.pm
+++ /dev/null
@@ -1,92 +0,0 @@
-package FS::Daemon;
-
-use vars qw( @ISA @EXPORT_OK );
-use vars qw( $pid_dir $me $pid_file $sigint $sigterm $logfile );
-use Exporter;
-use Fcntl qw(:flock);
-use POSIX qw(setsid);
-use IO::File;
-use Date::Format;
-
-#this is a simple refactoring of the stuff from freeside-queued, just to
-#avoid duplicate code. eventually this should use something from CPAN.
-
-@ISA = qw(Exporter);
-@EXPORT_OK = qw( daemonize1 drop_root daemonize2 sigint sigterm logfile );
-
-$pid_dir = '/var/run';
-
-sub daemonize1 {
- $me = shift;
-
- $pid_file = "$pid_dir/$me";
- $pid_file .= '.'.shift if scalar(@_);
- $pid_file .= '.pid';
-
- chdir "/" or die "Can't chdir to /: $!";
- open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
- defined(my $pid = fork) or die "Can't fork: $!";
- if ( $pid ) {
- print "$me started with pid $pid\n"; #logging to $log_file\n";
- exit unless $pid_file;
- my $pidfh = new IO::File ">$pid_file" or exit;
- print $pidfh "$pid\n";
- exit;
- }
-
- #sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
- #$SIG{CHLD} = \&REAPER;
- $sigterm = 0;
- $sigint = 0;
- $SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $sigint++; };
- $SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $sigterm++; };
-}
-
-sub drop_root {
- my $freeside_gid = scalar(getgrnam('freeside'))
- or die "can't find freeside group\n";
- $) = $freeside_gid;
- $( = $freeside_gid;
- #if freebsd can't setuid(), presumably it can't setgid() either. grr fleabsd
- ($(,$)) = ($),$();
- $) = $freeside_gid;
-
- $> = $FS::UID::freeside_uid;
- $< = $FS::UID::freeside_uid;
- #freebsd is sofa king broken, won't setuid()
- ($<,$>) = ($>,$<);
- $> = $FS::UID::freeside_uid;
-}
-
-sub daemonize2 {
- open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!";
- setsid or die "Can't start a new session: $!";
- open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
-
- $SIG{__DIE__} = \&_die;
- $SIG{__WARN__} = \&_logmsg;
-
- warn "$me starting\n";
-}
-
-sub sigint { $sigint; }
-sub sigterm { $sigterm; }
-
-sub logfile { $logfile = shift; } #_logmsg('test'); }
-
-sub _die {
- my $msg = shift;
- unlink $pid_file if -e $pid_file;
- _logmsg($msg);
-}
-
-sub _logmsg {
- chomp( my $msg = shift );
- my $log = new IO::File ">>$logfile";
- flock($log, LOCK_EX);
- seek($log, 0, 2);
- print $log "[". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n";
- flock($log, LOCK_UN);
- close $log;
-}
-
diff --git a/FS/FS/InitHandler.pm b/FS/FS/InitHandler.pm
deleted file mode 100644
index 5038cf3..0000000
--- a/FS/FS/InitHandler.pm
+++ /dev/null
@@ -1,91 +0,0 @@
-package FS::InitHandler;
-
-# this leaks memory under graceful restarts and i wouldn't use it on any
-# modern server. useful for very slow machines with memory to spare, just
-# always do a full restart
-
-use strict;
-use vars qw($DEBUG);
-use FS::UID qw(adminsuidsetup);
-use FS::Record;
-
-$DEBUG = 1;
-
-sub handler {
-
- use Date::Format;
- use Date::Parse;
- use Tie::IxHash;
- use HTML::Entities;
- use IO::Handle;
- use IO::File;
- use String::Approx;
- use HTML::Widgets::SelectLayers 0.02;
- #use FS::UID;
- #use FS::Record;
- use FS::Conf;
- use FS::CGI;
- use FS::Msgcat;
-
- use FS::agent;
- use FS::agent_type;
- use FS::domain_record;
- use FS::cust_bill;
- use FS::cust_bill_pay;
- use FS::cust_credit;
- use FS::cust_credit_bill;
- use FS::cust_main;
- use FS::cust_main_county;
- use FS::cust_pay;
- use FS::cust_pkg;
- use FS::cust_refund;
- use FS::cust_svc;
- use FS::nas;
- use FS::part_bill_event;
- use FS::part_pkg;
- use FS::part_referral;
- use FS::part_svc;
- use FS::pkg_svc;
- use FS::port;
- use FS::queue;
- use FS::raddb;
- use FS::session;
- use FS::svc_acct;
- use FS::svc_acct_pop;
- use FS::svc_domain;
- use FS::svc_forward;
- use FS::svc_www;
- use FS::type_pkgs;
- use FS::part_export;
- use FS::part_export_option;
- use FS::export_svc;
- use FS::msgcat;
-
- warn "[FS::InitHandler] handler called\n" if $DEBUG;
-
- #this is sure to be broken on freebsd
- $> = $FS::UID::freeside_uid;
-
- open(MAPSECRETS,"<$FS::UID::conf_dir/mapsecrets")
- or die "can't read $FS::UID::conf_dir/mapsecrets: $!";
-
- my %seen;
- while (<MAPSECRETS>) {
- next if /^\s*(#|$)/;
- /^([\w\-\.]+)\s(.*)$/
- or do { warn "strange line in mapsecrets: $_"; next; };
- my($user, $datasrc) = ($1, $2);
- next if $seen{$datasrc}++;
- warn "[FS::InitHandler] preloading $datasrc for $user\n" if $DEBUG;
- adminsuidsetup($user);
- }
-
- close MAPSECRETS;
-
- #lalala probably broken on freebsd
- ($<, $>) = ($>, $<);
- $< = 0;
-
-}
-
-1;
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
deleted file mode 100644
index 54467a1..0000000
--- a/FS/FS/Misc.pm
+++ /dev/null
@@ -1,576 +0,0 @@
-package FS::Misc;
-
-use strict;
-use vars qw ( @ISA @EXPORT_OK $DEBUG );
-use Exporter;
-use Carp;
-use Data::Dumper;
-#do NOT depend on any FS:: modules here, causes weird (sometimes unreproducable
-#until on client machine) dependancy loops. put them in FS::Misc::Something
-#instead
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw( send_email send_fax
- states_hash counties state_label
- card_types
- generate_ps do_print
- );
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::Misc - Miscellaneous subroutines
-
-=head1 SYNOPSIS
-
- use FS::Misc qw(send_email);
-
- send_email();
-
-=head1 DESCRIPTION
-
-Miscellaneous subroutines. This module contains miscellaneous subroutines
-called from multiple other modules. These are not OO or necessarily related,
-but are collected here to elimiate code duplication.
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item send_email OPTION => VALUE ...
-
-Options:
-
-I<from> - (required)
-
-I<to> - (required) comma-separated scalar or arrayref of recipients
-
-I<subject> - (required)
-
-I<content-type> - (optional) MIME type for the body
-
-I<body> - (required unless I<nobody> is true) arrayref of body text lines
-
-I<mimeparts> - (optional, but required if I<nobody> is true) arrayref of MIME::Entity->build PARAMHASH refs or MIME::Entity objects. These will be passed as arguments to MIME::Entity->attach().
-
-I<nobody> - (optional) when set true, send_email will ignore the I<body> option and simply construct a message with the given I<mimeparts>. In this case,
-I<content-type>, if specified, overrides the default "multipart/mixed" for the outermost MIME container.
-
-I<content-encoding> - (optional) when using nobody, optional top-level MIME
-encoding which, if specified, overrides the default "7bit".
-
-I<type> - (optional) type parameter for multipart/related messages
-
-=cut
-
-use vars qw( $conf );
-use Date::Format;
-use Mail::Header;
-use Mail::Internet 2.00;
-use MIME::Entity;
-use FS::UID;
-
-FS::UID->install_callback( sub {
- $conf = new FS::Conf;
-} );
-
-sub send_email {
- my(%options) = @_;
- if ( $DEBUG ) {
- my %doptions = %options;
- $doptions{'body'} = '(full body not shown in debug)';
- warn "FS::Misc::send_email called with options:\n ". Dumper(\%doptions);
-# join("\n", map { " $_: ". $options{$_} } keys %options ). "\n"
- }
-
- $ENV{MAILADDRESS} = $options{'from'};
- my $to = ref($options{to}) ? join(', ', @{ $options{to} } ) : $options{to};
-
- my @mimeargs = ();
- my @mimeparts = ();
- if ( $options{'nobody'} ) {
-
- croak "'mimeparts' option required when 'nobody' option given\n"
- unless $options{'mimeparts'};
-
- @mimeparts = @{$options{'mimeparts'}};
-
- @mimeargs = (
- 'Type' => ( $options{'content-type'} || 'multipart/mixed' ),
- 'Encoding' => ( $options{'content-encoding'} || '7bit' ),
- );
-
- } else {
-
- @mimeparts = @{$options{'mimeparts'}}
- if ref($options{'mimeparts'}) eq 'ARRAY';
-
- if (scalar(@mimeparts)) {
-
- @mimeargs = (
- 'Type' => 'multipart/mixed',
- 'Encoding' => '7bit',
- );
-
- unshift @mimeparts, {
- 'Type' => ( $options{'content-type'} || 'text/plain' ),
- 'Data' => $options{'body'},
- 'Encoding' => ( $options{'content-type'} ? '-SUGGEST' : '7bit' ),
- 'Disposition' => 'inline',
- };
-
- } else {
-
- @mimeargs = (
- 'Type' => ( $options{'content-type'} || 'text/plain' ),
- 'Data' => $options{'body'},
- 'Encoding' => ( $options{'content-type'} ? '-SUGGEST' : '7bit' ),
- );
-
- }
-
- }
-
- my $domain;
- if ( $options{'from'} =~ /\@([\w\.\-]+)/ ) {
- $domain = $1;
- } else {
- warn 'no domain found in invoice from address '. $options{'from'}.
- '; constructing Message-ID @example.com';
- $domain = 'example.com';
- }
- my $message_id = join('.', rand()*(2**32), $$, time). "\@$domain";
-
- my $message = MIME::Entity->build(
- 'From' => $options{'from'},
- 'To' => $to,
- 'Sender' => $options{'from'},
- 'Reply-To' => $options{'from'},
- 'Date' => time2str("%a, %d %b %Y %X %z", time),
- 'Subject' => $options{'subject'},
- 'Message-ID' => "<$message_id>",
- @mimeargs,
- );
-
- if ( $options{'type'} ) {
- #false laziness w/cust_bill::generate_email
- $message->head->replace('Content-type',
- $message->mime_type.
- '; boundary="'. $message->head->multipart_boundary. '"'.
- '; type='. $options{'type'}
- );
- }
-
- foreach my $part (@mimeparts) {
-
- if ( UNIVERSAL::isa($part, 'MIME::Entity') ) {
-
- warn "attaching MIME part from MIME::Entity object\n"
- if $DEBUG;
- $message->add_part($part);
-
- } elsif ( ref($part) eq 'HASH' ) {
-
- warn "attaching MIME part from hashref:\n".
- join("\n", map " $_: ".$part->{$_}, keys %$part ). "\n"
- if $DEBUG;
- $message->attach(%$part);
-
- } else {
- croak "mimepart $part isn't a hashref or MIME::Entity object!";
- }
-
- }
-
- my $smtpmachine = $conf->config('smtpmachine');
- $!=0;
-
- $message->mysmtpsend( 'Host' => $smtpmachine,
- 'MailFrom' => $options{'from'},
- );
-
-}
-
-#this kludges a "mysmtpsend" method into Mail::Internet for send_email above
-#now updated for MailTools v2!
-package Mail::Internet;
-
-use Mail::Address;
-use Net::SMTP;
-use Net::Domain;
-
-sub Mail::Internet::mysmtpsend($@) {
- my ($self, %opt) = @_;
-
- my $host = $opt{Host};
- my $envelope = $opt{MailFrom}; # || mailaddress();
- my $quit = 1;
-
- my ($smtp, @hello);
-
- push @hello, Hello => $opt{Hello}
- if defined $opt{Hello};
-
- push @hello, Port => $opt{Port}
- if exists $opt{Port};
-
- push @hello, Debug => $opt{Debug}
- if exists $opt{Debug};
-
-# if(!defined $host)
-# { local $SIG{__DIE__};
-# my @hosts = qw(mailhost localhost);
-# unshift @hosts, split /\:/, $ENV{SMTPHOSTS}
-# if defined $ENV{SMTPHOSTS};
-#
-# foreach $host (@hosts)
-# { $smtp = eval { Net::SMTP->new($host, @hello) };
-# last if defined $smtp;
-# }
-# }
-# elsif(ref($host) && UNIVERSAL::isa($host,'Net::SMTP'))
- if(ref($host) && UNIVERSAL::isa($host,'Net::SMTP'))
- { $smtp = $host;
- $quit = 0;
- }
- else
- { #local $SIG{__DIE__};
- #$smtp = eval { Net::SMTP->new($host, @hello) };
- $smtp = Net::SMTP->new($host, @hello);
- }
-
- unless ( defined($smtp) ) {
- my $err = $!;
- $err =~ s/Invalid argument/Unknown host/;
- return "can't connect to $host: $err"
- }
-
- my $head = $self->cleaned_header_dup;
-
- $head->delete('Bcc');
-
- # Who is it to
-
- my @rcpt = map { ref $_ ? @$_ : $_ } grep { defined } @opt{'To','Cc','Bcc'};
- @rcpt = map { $head->get($_) } qw(To Cc Bcc)
- unless @rcpt;
-
- my @addr = map {$_->address} Mail::Address->parse(@rcpt);
- #@addr or return ();
- return 'No valid destination addresses found!'
- unless(@addr);
-
- # Send it
-
- my $ok = $smtp->mail($envelope)
- && $smtp->to(@addr)
- && $smtp->data(join("", @{$head->header}, "\n", @{$self->body}));
-
- #$quit && $smtp->quit;
- #$ok ? @addr : ();
- if ( $ok ) {
- $quit && $smtp->quit;
- return '';
- } else {
- return $smtp->code. ' '. $smtp->message;
- }
-}
-package FS::Misc;
-#eokludge
-
-=item send_fax OPTION => VALUE ...
-
-Options:
-
-I<dialstring> - (required) 10-digit phone number w/ area code
-
-I<docdata> - (required) Array ref containing PostScript or TIFF Class F document
-
--or-
-
-I<docfile> - (required) Filename of PostScript TIFF Class F document
-
-...any other options will be passed to L<Fax::Hylafax::Client::sendfax>
-
-
-=cut
-
-sub send_fax {
-
- my %options = @_;
-
- die 'HylaFAX support has not been configured.'
- unless $conf->exists('hylafax');
-
- eval {
- require Fax::Hylafax::Client;
- };
-
- if ($@) {
- if ($@ =~ /^Can't locate Fax.*/) {
- die "You must have Fax::Hylafax::Client installed to use invoice faxing."
- } else {
- die $@;
- }
- }
-
- my %hylafax_opts = map { split /\s+/ } $conf->config('hylafax');
-
- die 'Called send_fax without a \'dialstring\'.'
- unless exists($options{'dialstring'});
-
- if (exists($options{'docdata'}) and ref($options{'docdata'}) eq 'ARRAY') {
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- my $fh = new File::Temp(
- TEMPLATE => 'faxdoc.'. $options{'dialstring'} . '.XXXXXXXX',
- DIR => $dir,
- UNLINK => 0,
- ) or die "can't open temp file: $!\n";
-
- $options{docfile} = $fh->filename;
-
- print $fh @{$options{'docdata'}};
- close $fh;
-
- delete $options{'docdata'};
- }
-
- die 'Called send_fax without a \'docfile\' or \'docdata\'.'
- unless exists($options{'docfile'});
-
- #FIXME: Need to send canonical dialstring to HylaFAX, but this only
- # works in the US.
-
- $options{'dialstring'} =~ s/[^\d\+]//g;
- if ($options{'dialstring'} =~ /^\d{10}$/) {
- $options{dialstring} = '+1' . $options{'dialstring'};
- } else {
- return 'Invalid dialstring ' . $options{'dialstring'} . '.';
- }
-
- my $faxjob = &Fax::Hylafax::Client::sendfax(%options, %hylafax_opts);
-
- if ($faxjob->success) {
- warn "Successfully queued fax to '$options{dialstring}' with jobid " .
- $faxjob->jobid
- if $DEBUG;
- return '';
- } else {
- return 'Error while sending FAX: ' . $faxjob->trace;
- }
-
-}
-
-=item states_hash COUNTRY
-
-Returns a list of key/value pairs containing state (or other sub-country
-division) abbriviations and names.
-
-=cut
-
-use FS::Record qw(qsearch);
-use Locale::SubCountry;
-
-sub states_hash {
- my($country) = @_;
-
- my @states =
-# sort
- map { s/[\n\r]//g; $_; }
- map { $_->state; }
- qsearch({
- 'select' => 'state',
- 'table' => 'cust_main_county',
- 'hashref' => { 'country' => $country },
- 'extra_sql' => 'GROUP BY state',
- });
-
- #it could throw a fatal "Invalid country code" error (for example "AX")
- my $subcountry = eval { new Locale::SubCountry($country) }
- or return ( '', '(n/a)' );
-
- #"i see your schwartz is as big as mine!"
- map { ( $_->[0] => $_->[1] ) }
- sort { $a->[1] cmp $b->[1] }
- map { [ $_ => state_label($_, $subcountry) ] }
- @states;
-}
-
-=item counties STATE COUNTRY
-
-Returns a list of counties for this state and country.
-
-=cut
-
-sub counties {
- my( $state, $country ) = @_;
-
- sort map { s/[\n\r]//g; $_; }
- map { $_->county }
- qsearch({
- 'select' => 'DISTINCT county',
- 'table' => 'cust_main_county',
- 'hashref' => { 'state' => $state,
- 'country' => $country,
- },
- });
-}
-
-=item state_label STATE COUNTRY_OR_LOCALE_SUBCOUNRY_OBJECT
-
-=cut
-
-sub state_label {
- my( $state, $country ) = @_;
-
- unless ( ref($country) ) {
- $country = eval { new Locale::SubCountry($country) }
- or return'(n/a)';
-
- }
-
- # US kludge to avoid changing existing behaviour
- # also we actually *use* the abbriviations...
- my $full_name = $country->country_code eq 'US'
- ? ''
- : $country->full_name($state);
-
- $full_name = '' if $full_name eq 'unknown';
- $full_name =~ s/\(see also.*\)\s*$//;
- $full_name .= " ($state)" if $full_name;
-
- $full_name || $state || '(n/a)';
-
-}
-
-=item card_types
-
-Returns a hash reference of the accepted credit card types. Keys are shorter
-identifiers and values are the longer strings used by the system (see
-L<Business::CreditCard>).
-
-=cut
-
-#$conf from above
-
-sub card_types {
- my $conf = new FS::Conf;
-
- my %card_types = (
- #displayname #value (Business::CreditCard)
- "VISA" => "VISA card",
- "MasterCard" => "MasterCard",
- "Discover" => "Discover card",
- "American Express" => "American Express card",
- "Diner's Club/Carte Blanche" => "Diner's Club/Carte Blanche",
- "enRoute" => "enRoute",
- "JCB" => "JCB",
- "BankCard" => "BankCard",
- "Switch" => "Switch",
- "Solo" => "Solo",
- );
- my @conf_card_types = grep { ! /^\s*$/ } $conf->config('card-types');
- if ( @conf_card_types ) {
- #perhaps the hash is backwards for this, but this way works better for
- #usage in selfservice
- %card_types = map { $_ => $card_types{$_} }
- grep {
- my $d = $_;
- grep { $card_types{$d} eq $_ } @conf_card_types
- }
- keys %card_types;
- }
-
- \%card_types;
-}
-
-=item generate_ps FILENAME
-
-Returns an postscript rendition of the LaTex file, as a scalar.
-FILENAME does not contain the .tex suffix and is unlinked by this function.
-
-=cut
-
-use String::ShellQuote;
-
-sub generate_ps {
- my $file = shift;
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- chdir($dir);
-
- my $sfile = shell_quote $file;
-
- system("pslatex $sfile.tex >/dev/null 2>&1") == 0
- or die "pslatex $file.tex failed; see $file.log for details?\n";
- system("pslatex $sfile.tex >/dev/null 2>&1") == 0
- or die "pslatex $file.tex failed; see $file.log for details?\n";
-
- system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0
- or die "dvips failed";
-
- open(POSTSCRIPT, "<$file.ps")
- or die "can't open $file.ps: $! (error in LaTeX template?)\n";
-
- unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps", "$file.tex");
-
- my $ps = '';
-
- if ( $conf->exists('lpr-postscript_prefix') ) {
- my $prefix = $conf->config('lpr-postscript_prefix');
- $ps .= eval qq("$prefix");
- }
-
- while (<POSTSCRIPT>) {
- $ps .= $_;
- }
-
- close POSTSCRIPT;
-
- if ( $conf->exists('lpr-postscript_suffix') ) {
- my $suffix = $conf->config('lpr-postscript_suffix');
- $ps .= eval qq("$suffix");
- }
-
- return $ps;
-
-}
-
-=item print ARRAYREF
-
-Sends the lines in ARRAYREF to the printer.
-
-=cut
-
-use IPC::Run3;
-
-sub do_print {
- my $data = shift;
-
- my $lpr = $conf->config('lpr');
-
- my $outerr = '';
- run3 $lpr, $data, \$outerr, \$outerr;
- if ( $? ) {
- $outerr = ": $outerr" if length($outerr);
- die "Error from $lpr (exit status ". ($?>>8). ")$outerr\n";
- }
-
-}
-
-=back
-
-=head1 BUGS
-
-This package exists.
-
-=head1 SEE ALSO
-
-L<FS::UID>, L<FS::CGI>, L<FS::Record>, the base documentation.
-
-L<Fax::Hylafax::Client>
-
-=cut
-
-1;
diff --git a/FS/FS/Misc/prune.pm b/FS/FS/Misc/prune.pm
deleted file mode 100644
index 371f31c..0000000
--- a/FS/FS/Misc/prune.pm
+++ /dev/null
@@ -1,126 +0,0 @@
-package FS::Misc::prune;
-
-use strict;
-use vars qw ( @ISA @EXPORT_OK $DEBUG );
-use Exporter;
-use FS::Record qw(dbh qsearch);
-use FS::cust_credit_refund;
-#use FS::cust_credit_bill;
-#use FS::cust_bill_pay;
-#use FS::cust_pay_refund;
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw( prune_applications );
-
-=head1 NAME
-
-FS::Misc::prune - misc. pruning subroutines
-
-=head1 SYNOPSIS
-
-use FS::Misc::prune qw(prune_applications);
-
-prune_applications();
-
-=item prune_applications OPTION_HASH
-
-Removes applications of credits to refunds in the event that the database
-is corrupt and either the credits or refunds are missing (see
-L<FS::cust_credit>, L<FS::cust_refund>, and L<FS::cust_credit_refund>).
-If the OPTION_HASH contains the element 'dry_run' then a report of
-affected records is returned rather than actually deleting the records.
-
-=cut
-
-sub prune_applications {
- my $options = shift;
- my $dbh = dbh
-
- local $DEBUG = 1 if exists($options->{debug});
- my $ccr = <<EOW;
- WHERE
- 0 = (select count(*) from cust_credit
- where cust_credit_refund.crednum = cust_credit.crednum)
- or
- 0 = (select count(*) from cust_refund
- where cust_credit_refund.refundnum = cust_refund.refundnum)
-EOW
- my $ccb = <<EOW;
- WHERE
- 0 = (select count(*) from cust_credit
- where cust_credit_bill.crednum = cust_credit.crednum)
- or
- 0 = (select count(*) from cust_bill
- where cust_credit_bill.invnum = cust_bill.invnum)
-EOW
- my $cbp = <<EOW;
- WHERE
- 0 = (select count(*) from cust_bill
- where cust_bill_pay.invnum = cust_bill.invnum)
- or
- 0 = (select count(*) from cust_pay
- where cust_bill_pay.paynum = cust_pay.paynum)
-EOW
- my $cpr = <<EOW;
- WHERE
- 0 = (select count(*) from cust_pay
- where cust_pay_refund.paynum = cust_pay.paynum)
- or
- 0 = (select count(*) from cust_refund
- where cust_pay_refund.refundnum = cust_refund.refundnum)
-EOW
-
- my %strays = (
- 'cust_credit_refund' => { clause => $ccr,
- link1 => 'crednum',
- link2 => 'refundnum',
- },
-# 'cust_credit_bill' => { clause => $ccb,
-# link1 => 'crednum',
-# link2 => 'refundnum',
-# },
-# 'cust_bill_pay' => { clause => $cbp,
-# link1 => 'crednum',
-# link2 => 'refundnum',
-# },
-# 'cust_pay_refund' => { clause => $cpr,
-# link1 => 'crednum',
-# link2 => 'refundnum',
-# },
- );
-
- if ( exists($options->{dry_run}) ) {
- my @response = ();
- foreach my $table (keys %strays) {
- my $clause = $strays{$table}->{clause};
- my $link1 = $strays{$table}->{link1};
- my $link2 = $strays{$table}->{link2};
- my @rec = qsearch($table, {}, '', $clause);
- my $keyname = $rec[0]->primary_key if $rec[0];
- foreach (@rec) {
- push @response, "$table " .$_->$keyname . " claims attachment to ".
- "$link1 " . $_->$link1 . " and $link2 " . $_->$link2 . "\n";
- }
- }
- return (@response);
- } else {
- foreach (keys %strays) {
- my $statement = "DELETE FROM $_ " . $strays{$_}->{clause};
- warn $statement if $DEBUG;
- my $sth = $dbh->prepare($statement)
- or die $dbh->errstr;
- $sth->execute
- or die $sth->errstr;
- }
- return ();
- }
-}
-
-=back
-
-=head1 BUGS
-
-=cut
-
-1;
-
diff --git a/FS/FS/Msgcat.pm b/FS/FS/Msgcat.pm
deleted file mode 100644
index 625743d..0000000
--- a/FS/FS/Msgcat.pm
+++ /dev/null
@@ -1,98 +0,0 @@
-package FS::Msgcat;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK $conf $locale $debug );
-use Exporter;
-use FS::UID;
-#use FS::Record qw( qsearchs ); # wtf? won't import...
-use FS::Record;
-use FS::Conf;
-use FS::msgcat;
-
-@ISA = qw(Exporter);
-@EXPORT_OK = qw( gettext geterror );
-
-$FS::UID::callback{'Msgcat'} = sub {
- $conf = new FS::Conf;
- $locale = $conf->config('locale') || 'en_US';
- $debug = $conf->exists('show-msgcat-codes')
-};
-
-=head1 NAME
-
-FS::Msgcat - Message catalog functions
-
-=head1 SYNOPSIS
-
- use FS::Msgcat qw(gettext geterror);
-
- #simple interface for retreiving messages...
- $message = gettext('msgcode');
- #or errors (includes the error code)
- $message = geterror('msgcode');
-
-=head1 DESCRIPTION
-
-FS::Msgcat provides functions to use the message catalog. If you want to
-maintain the message catalog database, see L<FS::msgcat> instead.
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item gettext MSGCODE
-
-Returns the full message for the supplied message code.
-
-=cut
-
-sub gettext {
- $debug ? geterror(@_) : _gettext(@_);
-}
-
-sub _gettext {
- my $msgcode = shift;
- my $msgcat = FS::Record::qsearchs('msgcat', {
- 'msgcode' => $msgcode,
- 'locale' => $locale
- } );
- if ( $msgcat ) {
- $msgcat->msg;
- } else {
- warn "WARNING: message for msgcode $msgcode in locale $locale not found";
- $msgcode;
- }
-
-}
-
-=item geterror MSGCODE
-
-Returns the full message for the supplied message code, including the message
-code.
-
-=cut
-
-sub geterror {
- my $msgcode = shift;
- my $msg = _gettext($msgcode);
- if ( $msg eq $msgcode ) {
- "Error code $msgcode (message for locale $locale not found)";
- } else {
- "$msg (error code $msgcode)";
- }
-}
-
-=back
-
-=head1 BUGS
-
-i18n/l10n, eek
-
-=head1 SEE ALSO
-
-L<FS::msgcat>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/Pony.pm b/FS/FS/Pony.pm
deleted file mode 100644
index c37dd78..0000000
--- a/FS/FS/Pony.pm
+++ /dev/null
@@ -1,23 +0,0 @@
-package FS::Pony;
-
-=head1 NAME
-
-FS::Pony - A pony
-
-=head1 SYNOPSYS
-
-use FS::Pony; # <-- yours!
-
-=head1 DESCRIPTION
-
-We told you it came with a pony.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-http://420.am/~ivan/nopony.jpg
-
-=cut
-
-1;
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
deleted file mode 100644
index db94003..0000000
--- a/FS/FS/Record.pm
+++ /dev/null
@@ -1,2351 +0,0 @@
-package FS::Record;
-
-use strict;
-use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG
- $conf $me
- %virtual_fields_cache $nowarn_identical $no_update_diff );
-use Exporter;
-use Carp qw(carp cluck croak confess);
-use File::CounterFile;
-use Locale::Country;
-use DBI qw(:sql_types);
-use DBIx::DBSchema 0.33;
-use FS::UID qw(dbh getotaker datasrc driver_name);
-use FS::CurrentUser;
-use FS::Schema qw(dbdef);
-use FS::SearchCache;
-use FS::Msgcat qw(gettext);
-use FS::Conf;
-
-use FS::part_virtual_field;
-
-use Tie::IxHash;
-
-@ISA = qw(Exporter);
-
-#export dbdef for now... everything else expects to find it here
-@EXPORT_OK = qw(dbh fields hfields qsearch qsearchs dbdef jsearch str2time_sql);
-
-$DEBUG = 0;
-$me = '[FS::Record]';
-
-$nowarn_identical = 0;
-$no_update_diff = 0;
-
-my $rsa_module;
-my $rsa_loaded;
-my $rsa_encrypt;
-my $rsa_decrypt;
-
-FS::UID->install_callback( sub {
- $conf = new FS::Conf;
- $File::CounterFile::DEFAULT_DIR = $conf->base_dir . "/counters.". datasrc;
-} );
-
-
-=head1 NAME
-
-FS::Record - Database record objects
-
-=head1 SYNOPSIS
-
- use FS::Record;
- use FS::Record qw(dbh fields qsearch qsearchs);
-
- $record = new FS::Record 'table', \%hash;
- $record = new FS::Record 'table', { 'column' => 'value', ... };
-
- $record = qsearchs FS::Record 'table', \%hash;
- $record = qsearchs FS::Record 'table', { 'column' => 'value', ... };
- @records = qsearch FS::Record 'table', \%hash;
- @records = qsearch FS::Record 'table', { 'column' => 'value', ... };
-
- $table = $record->table;
- $dbdef_table = $record->dbdef_table;
-
- $value = $record->get('column');
- $value = $record->getfield('column');
- $value = $record->column;
-
- $record->set( 'column' => 'value' );
- $record->setfield( 'column' => 'value' );
- $record->column('value');
-
- %hash = $record->hash;
-
- $hashref = $record->hashref;
-
- $error = $record->insert;
-
- $error = $record->delete;
-
- $error = $new_record->replace($old_record);
-
- # external use deprecated - handled by the database (at least for Pg, mysql)
- $value = $record->unique('column');
-
- $error = $record->ut_float('column');
- $error = $record->ut_floatn('column');
- $error = $record->ut_number('column');
- $error = $record->ut_numbern('column');
- $error = $record->ut_snumber('column');
- $error = $record->ut_snumbern('column');
- $error = $record->ut_money('column');
- $error = $record->ut_text('column');
- $error = $record->ut_textn('column');
- $error = $record->ut_alpha('column');
- $error = $record->ut_alphan('column');
- $error = $record->ut_phonen('column');
- $error = $record->ut_anything('column');
- $error = $record->ut_name('column');
-
- $quoted_value = _quote($value,'table','field');
-
- #deprecated
- $fields = hfields('table');
- if ( $fields->{Field} ) { # etc.
-
- @fields = fields 'table'; #as a subroutine
- @fields = $record->fields; #as a method call
-
-
-=head1 DESCRIPTION
-
-(Mostly) object-oriented interface to database records. Records are currently
-implemented on top of DBI. FS::Record is intended as a base class for
-table-specific classes to inherit from, i.e. FS::cust_main.
-
-=head1 CONSTRUCTORS
-
-=over 4
-
-=item new [ TABLE, ] HASHREF
-
-Creates a new record. It doesn't store it in the database, though. See
-L<"insert"> for that.
-
-Note that the object stores this hash reference, not a distinct copy of the
-hash it points to. You can ask the object for a copy with the I<hash>
-method.
-
-TABLE can only be omitted when a dervived class overrides the table method.
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
- bless ($self, $class);
-
- unless ( defined ( $self->table ) ) {
- $self->{'Table'} = shift;
- carp "warning: FS::Record::new called with table name ". $self->{'Table'};
- }
-
- $self->{'Hash'} = shift;
-
- foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) {
- $self->{'Hash'}{$field}='';
- }
-
- $self->_rebless if $self->can('_rebless');
-
- $self->{'modified'} = 0;
-
- $self->_cache($self->{'Hash'}, shift) if $self->can('_cache') && @_;
-
- $self;
-}
-
-sub new_or_cached {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
- bless ($self, $class);
-
- $self->{'Table'} = shift unless defined ( $self->table );
-
- my $hashref = $self->{'Hash'} = shift;
- my $cache = shift;
- if ( defined( $cache->cache->{$hashref->{$cache->key}} ) ) {
- my $obj = $cache->cache->{$hashref->{$cache->key}};
- $obj->_cache($hashref, $cache) if $obj->can('_cache');
- $obj;
- } else {
- $cache->cache->{$hashref->{$cache->key}} = $self->new($hashref, $cache);
- }
-
-}
-
-sub create {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
- bless ($self, $class);
- if ( defined $self->table ) {
- cluck "create constructor is deprecated, use new!";
- $self->new(@_);
- } else {
- croak "FS::Record::create called (not from a subclass)!";
- }
-}
-
-=item qsearch PARAMS_HASHREF | TABLE, HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ, ADDL_FROM
-
-Searches the database for all records matching (at least) the key/value pairs
-in HASHREF. Returns all the records found as `FS::TABLE' objects if that
-module is loaded (i.e. via `use FS::cust_main;'), otherwise returns FS::Record
-objects.
-
-The preferred usage is to pass a hash reference of named parameters:
-
- my @records = qsearch( {
- 'table' => 'table_name',
- 'hashref' => { 'field' => 'value'
- 'field' => { 'op' => '<',
- 'value' => '420',
- },
- },
-
- #these are optional...
- 'select' => '*',
- 'extra_sql' => 'AND field ',
- 'order_by' => 'ORDER BY something',
- #'cache_obj' => '', #optional
- 'addl_from' => 'LEFT JOIN othtable USING ( field )',
- 'debug' => 1,
- }
- );
-
-Much code still uses old-style positional parameters, this is also probably
-fine in the common case where there are only two parameters:
-
- my @records = qsearch( 'table', { 'field' => 'value' } );
-
-###oops, argh, FS::Record::new only lets us create database fields.
-#Normal behaviour if SELECT is not specified is `*', as in
-#C<SELECT * FROM table WHERE ...>. However, there is an experimental new
-#feature where you can specify SELECT - remember, the objects returned,
-#although blessed into the appropriate `FS::TABLE' package, will only have the
-#fields you specify. This might have unwanted results if you then go calling
-#regular FS::TABLE methods
-#on it.
-
-=cut
-
-sub qsearch {
- my($stable, $record, $select, $extra_sql, $order_by, $cache, $addl_from );
- my $debug = '';
- if ( ref($_[0]) ) { #hashref for now, eventually maybe accept a list too
- my $opt = shift;
- $stable = $opt->{'table'} or die "table name is required";
- $record = $opt->{'hashref'} || {};
- $select = $opt->{'select'} || '*';
- $extra_sql = $opt->{'extra_sql'} || '';
- $order_by = $opt->{'order_by'} || '';
- $cache = $opt->{'cache_obj'} || '';
- $addl_from = $opt->{'addl_from'} || '';
- $debug = $opt->{'debug'} || '';
- } else {
- ($stable, $record, $select, $extra_sql, $cache, $addl_from ) = @_;
- $select ||= '*';
- }
-
- #$stable =~ /^([\w\_]+)$/ or die "Illegal table: $table";
- #for jsearch
- $stable =~ /^([\w\s\(\)\.\,\=]+)$/ or die "Illegal table: $stable";
- $stable = $1;
- my $dbh = dbh;
-
- my $table = $cache ? $cache->table : $stable;
- my $dbdef_table = dbdef->table($table)
- or die "No schema for table $table found - ".
- "do you need to run freeside-upgrade?";
- my $pkey = $dbdef_table->primary_key;
-
- my @real_fields = grep exists($record->{$_}), real_fields($table);
- my @virtual_fields;
- if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
- @virtual_fields = grep exists($record->{$_}), "FS::$table"->virtual_fields;
- } else {
- cluck "warning: FS::$table not loaded; virtual fields not searchable";
- @virtual_fields = ();
- }
-
- my $statement = "SELECT $select FROM $stable";
- $statement .= " $addl_from" if $addl_from;
- if ( @real_fields or @virtual_fields ) {
- $statement .= ' WHERE '. join(' AND ',
- get_real_fields($table, $record, \@real_fields) ,
- get_virtual_fields($table, $pkey, $record, \@virtual_fields),
- );
- }
-
- $statement .= " $extra_sql" if defined($extra_sql);
- $statement .= " $order_by" if defined($order_by);
-
- warn "[debug]$me $statement\n" if $DEBUG > 1 || $debug;
- my $sth = $dbh->prepare($statement)
- or croak "$dbh->errstr doing $statement";
-
- my $bind = 1;
-
- foreach my $field (
- grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields
- ) {
- if ( $record->{$field} =~ /^\d+(\.\d+)?$/
- && dbdef->table($table)->column($field)->type =~ /(int|(big)?serial)/i
- ) {
- $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_INTEGER } );
- } else {
- $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_VARCHAR } );
- }
- }
-
-# $sth->execute( map $record->{$_},
-# grep defined( $record->{$_} ) && $record->{$_} ne '', @fields
-# ) or croak "Error executing \"$statement\": ". $sth->errstr;
-
- $sth->execute or croak "Error executing \"$statement\": ". $sth->errstr;
-
- if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
- @virtual_fields = "FS::$table"->virtual_fields;
- } else {
- cluck "warning: FS::$table not loaded; virtual fields not returned either";
- @virtual_fields = ();
- }
-
- my %result;
- tie %result, "Tie::IxHash";
- my @stuff = @{ $sth->fetchall_arrayref( {} ) };
- if ( $pkey && scalar(@stuff) && $stuff[0]->{$pkey} ) {
- %result = map { $_->{$pkey}, $_ } @stuff;
- } else {
- @result{@stuff} = @stuff;
- }
-
- $sth->finish;
-
- if ( keys(%result) and @virtual_fields ) {
- $statement =
- "SELECT virtual_field.recnum, part_virtual_field.name, ".
- "virtual_field.value ".
- "FROM part_virtual_field JOIN virtual_field USING (vfieldpart) ".
- "WHERE part_virtual_field.dbtable = '$table' AND ".
- "virtual_field.recnum IN (".
- join(',', keys(%result)). ") AND part_virtual_field.name IN ('".
- join(q!', '!, @virtual_fields) . "')";
- warn "[debug]$me $statement\n" if $DEBUG > 1;
- $sth = $dbh->prepare($statement) or croak "$dbh->errstr doing $statement";
- $sth->execute or croak "Error executing \"$statement\": ". $sth->errstr;
-
- foreach (@{ $sth->fetchall_arrayref({}) }) {
- my $recnum = $_->{recnum};
- my $name = $_->{name};
- my $value = $_->{value};
- if (exists($result{$recnum})) {
- $result{$recnum}->{$name} = $value;
- }
- }
- }
- my @return;
- if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
- if ( eval 'FS::'. $table. '->can(\'new\')' eq \&new ) {
- #derivied class didn't override new method, so this optimization is safe
- if ( $cache ) {
- @return = map {
- new_or_cached( "FS::$table", { %{$_} }, $cache )
- } values(%result);
- } else {
- @return = map {
- new( "FS::$table", { %{$_} } )
- } values(%result);
- }
- } else {
- #okay, its been tested
- # warn "untested code (class FS::$table uses custom new method)";
- @return = map {
- eval 'FS::'. $table. '->new( { %{$_} } )';
- } values(%result);
- }
-
- # Check for encrypted fields and decrypt them.
- ## only in the local copy, not the cached object
- if ( $conf && $conf->exists('encryption') # $conf doesn't exist when doing
- # the initial search for
- # access_user
- && eval 'defined(@FS::'. $table . '::encrypted_fields)') {
- foreach my $record (@return) {
- foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
- # Set it directly... This may cause a problem in the future...
- $record->setfield($field, $record->decrypt($record->getfield($field)));
- }
- }
- }
- } else {
- cluck "warning: FS::$table not loaded; returning FS::Record objects";
- @return = map {
- FS::Record->new( $table, { %{$_} } );
- } values(%result);
- }
- return @return;
-}
-
-## makes this easier to read
-
-sub get_virtual_fields {
- my $table = shift;
- my $pkey = shift;
- my $record = shift;
- my $virtual_fields = shift;
-
- return
- ( map {
- my $op = '=';
- my $column = $_;
- if ( ref($record->{$_}) ) {
- $op = $record->{$_}{'op'} if $record->{$_}{'op'};
- if ( uc($op) eq 'ILIKE' ) {
- $op = 'LIKE';
- $record->{$_}{'value'} = lc($record->{$_}{'value'});
- $column = "LOWER($_)";
- }
- $record->{$_} = $record->{$_}{'value'};
- }
-
- # ... EXISTS ( SELECT name, value FROM part_virtual_field
- # JOIN virtual_field
- # ON part_virtual_field.vfieldpart = virtual_field.vfieldpart
- # WHERE recnum = svc_acct.svcnum
- # AND (name, value) = ('egad', 'brain') )
-
- my $value = $record->{$_};
-
- my $subq;
-
- $subq = ($value ? 'EXISTS ' : 'NOT EXISTS ') .
- "( SELECT part_virtual_field.name, virtual_field.value ".
- "FROM part_virtual_field JOIN virtual_field ".
- "ON part_virtual_field.vfieldpart = virtual_field.vfieldpart ".
- "WHERE virtual_field.recnum = ${table}.${pkey} ".
- "AND part_virtual_field.name = '${column}'".
- ($value ?
- " AND virtual_field.value ${op} '${value}'"
- : "") . ")";
- $subq;
-
- } @{ $virtual_fields } ) ;
-}
-
-sub get_real_fields {
- my $table = shift;
- my $record = shift;
- my $real_fields = shift;
-
- ## this huge map was previously inline, just broke it out to help read the qsearch method, should be optimized for readability
- return (
- map {
-
- my $op = '=';
- my $column = $_;
- if ( ref($record->{$_}) ) {
- $op = $record->{$_}{'op'} if $record->{$_}{'op'};
- #$op = 'LIKE' if $op =~ /^ILIKE$/i && driver_name ne 'Pg';
- if ( uc($op) eq 'ILIKE' ) {
- $op = 'LIKE';
- $record->{$_}{'value'} = lc($record->{$_}{'value'});
- $column = "LOWER($_)";
- }
- $record->{$_} = $record->{$_}{'value'}
- }
-
- if ( ! defined( $record->{$_} ) || $record->{$_} eq '' ) {
- if ( $op eq '=' ) {
- if ( driver_name eq 'Pg' ) {
- my $type = dbdef->table($table)->column($column)->type;
- if ( $type =~ /(int|(big)?serial)/i ) {
- qq-( $column IS NULL )-;
- } else {
- qq-( $column IS NULL OR $column = '' )-;
- }
- } else {
- qq-( $column IS NULL OR $column = "" )-;
- }
- } elsif ( $op eq '!=' ) {
- if ( driver_name eq 'Pg' ) {
- my $type = dbdef->table($table)->column($column)->type;
- if ( $type =~ /(int|(big)?serial)/i ) {
- qq-( $column IS NOT NULL )-;
- } else {
- qq-( $column IS NOT NULL AND $column != '' )-;
- }
- } else {
- qq-( $column IS NOT NULL AND $column != "" )-;
- }
- } else {
- if ( driver_name eq 'Pg' ) {
- qq-( $column $op '' )-;
- } else {
- qq-( $column $op "" )-;
- }
- }
- } else {
- "$column $op ?";
- }
- } @{ $real_fields } );
-}
-
-=item by_key PRIMARY_KEY_VALUE
-
-This is a class method that returns the record with the given primary key
-value. This method is only useful in FS::Record subclasses. For example:
-
- my $cust_main = FS::cust_main->by_key(1); # retrieve customer with custnum 1
-
-is equivalent to:
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => 1 } );
-
-=cut
-
-sub by_key {
- my ($class, $pkey_value) = @_;
-
- my $table = $class->table
- or croak "No table for $class found";
-
- my $dbdef_table = dbdef->table($table)
- or die "No schema for table $table found - ".
- "do you need to create it or run dbdef-create?";
- my $pkey = $dbdef_table->primary_key
- or die "No primary key for table $table";
-
- return qsearchs($table, { $pkey => $pkey_value });
-}
-
-=item jsearch TABLE, HASHREF, SELECT, EXTRA_SQL, PRIMARY_TABLE, PRIMARY_KEY
-
-Experimental JOINed search method. Using this method, you can execute a
-single SELECT spanning multiple tables, and cache the results for subsequent
-method calls. Interface will almost definately change in an incompatible
-fashion.
-
-Arguments:
-
-=cut
-
-sub jsearch {
- my($table, $record, $select, $extra_sql, $ptable, $pkey ) = @_;
- my $cache = FS::SearchCache->new( $ptable, $pkey );
- my %saw;
- ( $cache,
- grep { !$saw{$_->getfield($pkey)}++ }
- qsearch($table, $record, $select, $extra_sql, $cache )
- );
-}
-
-=item qsearchs PARAMS_HASHREF | TABLE, HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ, ADDL_FROM
-
-Same as qsearch, except that if more than one record matches, it B<carp>s but
-returns the first. If this happens, you either made a logic error in asking
-for a single item, or your data is corrupted.
-
-=cut
-
-sub qsearchs { # $result_record = &FS::Record:qsearchs('table',\%hash);
- my $table = $_[0];
- my(@result) = qsearch(@_);
- cluck "warning: Multiple records in scalar search ($table)"
- if scalar(@result) > 1;
- #should warn more vehemently if the search was on a primary key?
- scalar(@result) ? ($result[0]) : ();
-}
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item table
-
-Returns the table name.
-
-=cut
-
-sub table {
-# cluck "warning: FS::Record::table deprecated; supply one in subclass!";
- my $self = shift;
- $self -> {'Table'};
-}
-
-=item dbdef_table
-
-Returns the DBIx::DBSchema::Table object for the table.
-
-=cut
-
-sub dbdef_table {
- my($self)=@_;
- my($table)=$self->table;
- dbdef->table($table);
-}
-
-=item primary_key
-
-Returns the primary key for the table.
-
-=cut
-
-sub primary_key {
- my $self = shift;
- my $pkey = $self->dbdef_table->primary_key;
-}
-
-=item get, getfield COLUMN
-
-Returns the value of the column/field/key COLUMN.
-
-=cut
-
-sub get {
- my($self,$field) = @_;
- # to avoid "Use of unitialized value" errors
- if ( defined ( $self->{Hash}->{$field} ) ) {
- $self->{Hash}->{$field};
- } else {
- '';
- }
-}
-sub getfield {
- my $self = shift;
- $self->get(@_);
-}
-
-=item set, setfield COLUMN, VALUE
-
-Sets the value of the column/field/key COLUMN to VALUE. Returns VALUE.
-
-=cut
-
-sub set {
- my($self,$field,$value) = @_;
- $self->{'modified'} = 1;
- $self->{'Hash'}->{$field} = $value;
-}
-sub setfield {
- my $self = shift;
- $self->set(@_);
-}
-
-=item AUTLOADED METHODS
-
-$record->column is a synonym for $record->get('column');
-
-$record->column('value') is a synonym for $record->set('column','value');
-
-=cut
-
-# readable/safe
-sub AUTOLOAD {
- my($self,$value)=@_;
- my($field)=$AUTOLOAD;
- $field =~ s/.*://;
- if ( defined($value) ) {
- confess "errant AUTOLOAD $field for $self (arg $value)"
- unless ref($self) && $self->can('setfield');
- $self->setfield($field,$value);
- } else {
- confess "errant AUTOLOAD $field for $self (no args)"
- unless ref($self) && $self->can('getfield');
- $self->getfield($field);
- }
-}
-
-# efficient
-#sub AUTOLOAD {
-# my $field = $AUTOLOAD;
-# $field =~ s/.*://;
-# if ( defined($_[1]) ) {
-# $_[0]->setfield($field, $_[1]);
-# } else {
-# $_[0]->getfield($field);
-# }
-#}
-
-=item hash
-
-Returns a list of the column/value pairs, usually for assigning to a new hash.
-
-To make a distinct duplicate of an FS::Record object, you can do:
-
- $new = new FS::Record ( $old->table, { $old->hash } );
-
-=cut
-
-sub hash {
- my($self) = @_;
- confess $self. ' -> hash: Hash attribute is undefined'
- unless defined($self->{'Hash'});
- %{ $self->{'Hash'} };
-}
-
-=item hashref
-
-Returns a reference to the column/value hash. This may be deprecated in the
-future; if there's a reason you can't just use the autoloaded or get/set
-methods, speak up.
-
-=cut
-
-sub hashref {
- my($self) = @_;
- $self->{'Hash'};
-}
-
-=item modified
-
-Returns true if any of this object's values have been modified with set (or via
-an autoloaded method). Doesn't yet recognize when you retreive a hashref and
-modify that.
-
-=cut
-
-sub modified {
- my $self = shift;
- $self->{'modified'};
-}
-
-=item select_for_update
-
-Selects this record with the SQL "FOR UPDATE" command. This can be useful as
-a mutex.
-
-=cut
-
-sub select_for_update {
- my $self = shift;
- my $primary_key = $self->primary_key;
- qsearchs( {
- 'select' => '*',
- 'table' => $self->table,
- 'hashref' => { $primary_key => $self->$primary_key() },
- 'extra_sql' => 'FOR UPDATE',
- } );
-}
-
-=item insert
-
-Inserts this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub insert {
- my $self = shift;
- my $saved = {};
-
- warn "$self -> insert" if $DEBUG;
-
- my $error = $self->check;
- return $error if $error;
-
- #single-field unique keys are given a value if false
- #(like MySQL's AUTO_INCREMENT or Pg SERIAL)
- foreach ( $self->dbdef_table->unique_singles) {
- $self->unique($_) unless $self->getfield($_);
- }
-
- #and also the primary key, if the database isn't going to
- my $primary_key = $self->dbdef_table->primary_key;
- my $db_seq = 0;
- if ( $primary_key ) {
- my $col = $self->dbdef_table->column($primary_key);
-
- $db_seq =
- uc($col->type) =~ /^(BIG)?SERIAL\d?/
- || ( driver_name eq 'Pg'
- && defined($col->default)
- && $col->default =~ /^nextval\(/i
- )
- || ( driver_name eq 'mysql'
- && defined($col->local)
- && $col->local =~ /AUTO_INCREMENT/i
- );
- $self->unique($primary_key) unless $self->getfield($primary_key) || $db_seq;
- }
-
- my $table = $self->table;
-
-
- # Encrypt before the database
- my $conf = new FS::Conf;
- if ($conf->exists('encryption') && defined(eval '@FS::'. $table . '::encrypted_fields')) {
- foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
- $self->{'saved'} = $self->getfield($field);
- $self->setfield($field, $self->encrypt($self->getfield($field)));
- }
- }
-
-
- #false laziness w/delete
- my @real_fields =
- grep { defined($self->getfield($_)) && $self->getfield($_) ne "" }
- real_fields($table)
- ;
- my @values = map { _quote( $self->getfield($_), $table, $_) } @real_fields;
- #eslaf
-
- my $statement = "INSERT INTO $table ";
- if ( @real_fields ) {
- $statement .=
- "( ".
- join( ', ', @real_fields ).
- ") VALUES (".
- join( ', ', @values ).
- ")"
- ;
- } else {
- $statement .= 'DEFAULT VALUES';
- }
- warn "[debug]$me $statement\n" if $DEBUG > 1;
- my $sth = dbh->prepare($statement) or return dbh->errstr;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- $sth->execute or return $sth->errstr;
-
- # get inserted id from the database, if applicable & needed
- if ( $db_seq && ! $self->getfield($primary_key) ) {
- warn "[debug]$me retreiving sequence from database\n" if $DEBUG;
-
- my $insertid = '';
-
- if ( driver_name eq 'Pg' ) {
-
- #my $oid = $sth->{'pg_oid_status'};
- #my $i_sql = "SELECT $primary_key FROM $table WHERE oid = ?";
-
- my $default = $self->dbdef_table->column($primary_key)->default;
- unless ( $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i ) {
- dbh->rollback if $FS::UID::AutoCommit;
- return "can't parse $table.$primary_key default value".
- " for sequence name: $default";
- }
- my $sequence = $1;
-
- my $i_sql = "SELECT currval('$sequence')";
- my $i_sth = dbh->prepare($i_sql) or do {
- dbh->rollback if $FS::UID::AutoCommit;
- return dbh->errstr;
- };
- $i_sth->execute() or do { #$i_sth->execute($oid)
- dbh->rollback if $FS::UID::AutoCommit;
- return $i_sth->errstr;
- };
- $insertid = $i_sth->fetchrow_arrayref->[0];
-
- } elsif ( driver_name eq 'mysql' ) {
-
- $insertid = dbh->{'mysql_insertid'};
- # work around mysql_insertid being null some of the time, ala RT :/
- unless ( $insertid ) {
- warn "WARNING: DBD::mysql didn't return mysql_insertid; ".
- "using SELECT LAST_INSERT_ID();";
- my $i_sql = "SELECT LAST_INSERT_ID()";
- my $i_sth = dbh->prepare($i_sql) or do {
- dbh->rollback if $FS::UID::AutoCommit;
- return dbh->errstr;
- };
- $i_sth->execute or do {
- dbh->rollback if $FS::UID::AutoCommit;
- return $i_sth->errstr;
- };
- $insertid = $i_sth->fetchrow_arrayref->[0];
- }
-
- } else {
-
- dbh->rollback if $FS::UID::AutoCommit;
- return "don't know how to retreive inserted ids from ". driver_name.
- ", try using counterfiles (maybe run dbdef-create?)";
-
- }
-
- $self->setfield($primary_key, $insertid);
-
- }
-
- my @virtual_fields =
- grep defined($self->getfield($_)) && $self->getfield($_) ne "",
- $self->virtual_fields;
- if (@virtual_fields) {
- my %v_values = map { $_, $self->getfield($_) } @virtual_fields;
-
- my $vfieldpart = $self->vfieldpart_hashref;
-
- my $v_statement = "INSERT INTO virtual_field(recnum, vfieldpart, value) ".
- "VALUES (?, ?, ?)";
-
- my $v_sth = dbh->prepare($v_statement) or do {
- dbh->rollback if $FS::UID::AutoCommit;
- return dbh->errstr;
- };
-
- foreach (keys(%v_values)) {
- $v_sth->execute($self->getfield($primary_key),
- $vfieldpart->{$_},
- $v_values{$_})
- or do {
- dbh->rollback if $FS::UID::AutoCommit;
- return $v_sth->errstr;
- };
- }
- }
-
-
- my $h_sth;
- if ( defined dbdef->table('h_'. $table) ) {
- my $h_statement = $self->_h_statement('insert');
- warn "[debug]$me $h_statement\n" if $DEBUG > 2;
- $h_sth = dbh->prepare($h_statement) or do {
- dbh->rollback if $FS::UID::AutoCommit;
- return dbh->errstr;
- };
- } else {
- $h_sth = '';
- }
- $h_sth->execute or return $h_sth->errstr if $h_sth;
-
- dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
-
- # Now that it has been saved, reset the encrypted fields so that $new
- # can still be used.
- foreach my $field (keys %{$saved}) {
- $self->setfield($field, $saved->{$field});
- }
-
- '';
-}
-
-=item add
-
-Depriciated (use insert instead).
-
-=cut
-
-sub add {
- cluck "warning: FS::Record::add deprecated!";
- insert @_; #call method in this scope
-}
-
-=item delete
-
-Delete this record from the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- my $statement = "DELETE FROM ". $self->table. " WHERE ". join(' AND ',
- map {
- $self->getfield($_) eq ''
- #? "( $_ IS NULL OR $_ = \"\" )"
- ? ( driver_name eq 'Pg'
- ? "$_ IS NULL"
- : "( $_ IS NULL OR $_ = \"\" )"
- )
- : "$_ = ". _quote($self->getfield($_),$self->table,$_)
- } ( $self->dbdef_table->primary_key )
- ? ( $self->dbdef_table->primary_key)
- : real_fields($self->table)
- );
- warn "[debug]$me $statement\n" if $DEBUG > 1;
- my $sth = dbh->prepare($statement) or return dbh->errstr;
-
- my $h_sth;
- if ( defined dbdef->table('h_'. $self->table) ) {
- my $h_statement = $self->_h_statement('delete');
- warn "[debug]$me $h_statement\n" if $DEBUG > 2;
- $h_sth = dbh->prepare($h_statement) or return dbh->errstr;
- } else {
- $h_sth = '';
- }
-
- my $primary_key = $self->dbdef_table->primary_key;
- my $v_sth;
- my @del_vfields;
- my $vfp = $self->vfieldpart_hashref;
- foreach($self->virtual_fields) {
- next if $self->getfield($_) eq '';
- unless(@del_vfields) {
- my $st = "DELETE FROM virtual_field WHERE recnum = ? AND vfieldpart = ?";
- $v_sth = dbh->prepare($st) or return dbh->errstr;
- }
- push @del_vfields, $_;
- }
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $rc = $sth->execute or return $sth->errstr;
- #not portable #return "Record not found, statement:\n$statement" if $rc eq "0E0";
- $h_sth->execute or return $h_sth->errstr if $h_sth;
- $v_sth->execute($self->getfield($primary_key), $vfp->{$_})
- or return $v_sth->errstr
- foreach (@del_vfields);
-
- dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
-
- #no need to needlessly destoy the data either (causes problems actually)
- #undef $self; #no need to keep object!
-
- '';
-}
-
-=item del
-
-Depriciated (use delete instead).
-
-=cut
-
-sub del {
- cluck "warning: FS::Record::del deprecated!";
- &delete(@_); #call method in this scope
-}
-
-=item replace OLD_RECORD
-
-Replace the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my ($new, $old) = (shift, shift);
-
- $old = $new->replace_old unless defined($old);
-
- warn "[debug]$me $new ->replace $old\n" if $DEBUG;
-
- if ( $new->can('replace_check') ) {
- my $error = $new->replace_check($old);
- return $error if $error;
- }
-
- return "Records not in same table!" unless $new->table eq $old->table;
-
- my $primary_key = $old->dbdef_table->primary_key;
- return "Can't change primary key $primary_key ".
- 'from '. $old->getfield($primary_key).
- ' to ' . $new->getfield($primary_key)
- if $primary_key
- && ( $old->getfield($primary_key) ne $new->getfield($primary_key) );
-
- my $error = $new->check;
- return $error if $error;
-
- # Encrypt for replace
- my $conf = new FS::Conf;
- my $saved = {};
- if ($conf->exists('encryption') && defined(eval '@FS::'. $new->table . '::encrypted_fields')) {
- foreach my $field (eval '@FS::'. $new->table . '::encrypted_fields') {
- $saved->{$field} = $new->getfield($field);
- $new->setfield($field, $new->encrypt($new->getfield($field)));
- }
- }
-
- #my @diff = grep $new->getfield($_) ne $old->getfield($_), $old->fields;
- my %diff = map { ($new->getfield($_) ne $old->getfield($_))
- ? ($_, $new->getfield($_)) : () } $old->fields;
-
- unless (keys(%diff) || $no_update_diff ) {
- carp "[warning]$me $new -> replace $old: records identical"
- unless $nowarn_identical;
- return '';
- }
-
- my $statement = "UPDATE ". $old->table. " SET ". join(', ',
- map {
- "$_ = ". _quote($new->getfield($_),$old->table,$_)
- } real_fields($old->table)
- ). ' WHERE '.
- join(' AND ',
- map {
-
- if ( $old->getfield($_) eq '' ) {
-
- #false laziness w/qsearch
- if ( driver_name eq 'Pg' ) {
- my $type = $old->dbdef_table->column($_)->type;
- if ( $type =~ /(int|(big)?serial)/i ) {
- qq-( $_ IS NULL )-;
- } else {
- qq-( $_ IS NULL OR $_ = '' )-;
- }
- } else {
- qq-( $_ IS NULL OR $_ = "" )-;
- }
-
- } else {
- "$_ = ". _quote($old->getfield($_),$old->table,$_);
- }
-
- } ( $primary_key ? ( $primary_key ) : real_fields($old->table) )
- )
- ;
- warn "[debug]$me $statement\n" if $DEBUG > 1;
- my $sth = dbh->prepare($statement) or return dbh->errstr;
-
- my $h_old_sth;
- if ( defined dbdef->table('h_'. $old->table) ) {
- my $h_old_statement = $old->_h_statement('replace_old');
- warn "[debug]$me $h_old_statement\n" if $DEBUG > 2;
- $h_old_sth = dbh->prepare($h_old_statement) or return dbh->errstr;
- } else {
- $h_old_sth = '';
- }
-
- my $h_new_sth;
- if ( defined dbdef->table('h_'. $new->table) ) {
- my $h_new_statement = $new->_h_statement('replace_new');
- warn "[debug]$me $h_new_statement\n" if $DEBUG > 2;
- $h_new_sth = dbh->prepare($h_new_statement) or return dbh->errstr;
- } else {
- $h_new_sth = '';
- }
-
- # For virtual fields we have three cases with different SQL
- # statements: add, replace, delete
- my $v_add_sth;
- my $v_rep_sth;
- my $v_del_sth;
- my (@add_vfields, @rep_vfields, @del_vfields);
- my $vfp = $old->vfieldpart_hashref;
- foreach(grep { exists($diff{$_}) } $new->virtual_fields) {
- if($diff{$_} eq '') {
- # Delete
- unless(@del_vfields) {
- my $st = "DELETE FROM virtual_field WHERE recnum = ? ".
- "AND vfieldpart = ?";
- warn "[debug]$me $st\n" if $DEBUG > 2;
- $v_del_sth = dbh->prepare($st) or return dbh->errstr;
- }
- push @del_vfields, $_;
- } elsif($old->getfield($_) eq '') {
- # Add
- unless(@add_vfields) {
- my $st = "INSERT INTO virtual_field (value, recnum, vfieldpart) ".
- "VALUES (?, ?, ?)";
- warn "[debug]$me $st\n" if $DEBUG > 2;
- $v_add_sth = dbh->prepare($st) or return dbh->errstr;
- }
- push @add_vfields, $_;
- } else {
- # Replace
- unless(@rep_vfields) {
- my $st = "UPDATE virtual_field SET value = ? ".
- "WHERE recnum = ? AND vfieldpart = ?";
- warn "[debug]$me $st\n" if $DEBUG > 2;
- $v_rep_sth = dbh->prepare($st) or return dbh->errstr;
- }
- push @rep_vfields, $_;
- }
- }
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $rc = $sth->execute or return $sth->errstr;
- #not portable #return "Record not found (or records identical)." if $rc eq "0E0";
- $h_old_sth->execute or return $h_old_sth->errstr if $h_old_sth;
- $h_new_sth->execute or return $h_new_sth->errstr if $h_new_sth;
-
- $v_del_sth->execute($old->getfield($primary_key),
- $vfp->{$_})
- or return $v_del_sth->errstr
- foreach(@del_vfields);
-
- $v_add_sth->execute($new->getfield($_),
- $old->getfield($primary_key),
- $vfp->{$_})
- or return $v_add_sth->errstr
- foreach(@add_vfields);
-
- $v_rep_sth->execute($new->getfield($_),
- $old->getfield($primary_key),
- $vfp->{$_})
- or return $v_rep_sth->errstr
- foreach(@rep_vfields);
-
- dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
-
- # Now that it has been saved, reset the encrypted fields so that $new
- # can still be used.
- foreach my $field (keys %{$saved}) {
- $new->setfield($field, $saved->{$field});
- }
-
- '';
-
-}
-
-sub replace_old {
- my( $self ) = shift;
- warn "[$me] replace called with no arguments; autoloading old record\n"
- if $DEBUG;
-
- my $primary_key = $self->dbdef_table->primary_key;
- if ( $primary_key ) {
- $self->by_key( $self->$primary_key() ) #this is what's returned
- or croak "can't find ". $self->table. ".$primary_key ".
- $self->$primary_key();
- } else {
- croak $self->table. " has no primary key; pass old record as argument";
- }
-
-}
-
-=item rep
-
-Depriciated (use replace instead).
-
-=cut
-
-sub rep {
- cluck "warning: FS::Record::rep deprecated!";
- replace @_; #call method in this scope
-}
-
-=item check
-
-Checks virtual fields (using check_blocks). Subclasses should still provide
-a check method to validate real fields, foreign keys, etc., and call this
-method via $self->SUPER::check.
-
-(FIXME: Should this method try to make sure that it I<is> being called from
-a subclass's check method, to keep the current semantics as far as possible?)
-
-=cut
-
-sub check {
- #confess "FS::Record::check not implemented; supply one in subclass!";
- my $self = shift;
-
- foreach my $field ($self->virtual_fields) {
- for ($self->getfield($field)) {
- # See notes on check_block in FS::part_virtual_field.
- eval $self->pvf($field)->check_block;
- if ( $@ ) {
- #this is bad, probably want to follow the stack backtrace up and see
- #wtf happened
- my $err = "Fatal error checking $field for $self";
- cluck "$err: $@";
- return "$err (see log for backtrace): $@";
-
- }
- $self->setfield($field, $_);
- }
- }
- '';
-}
-
-sub _h_statement {
- my( $self, $action, $time ) = @_;
-
- $time ||= time;
-
- my @fields =
- grep { defined($self->getfield($_)) && $self->getfield($_) ne "" }
- real_fields($self->table);
- ;
-
- # If we're encrypting then don't ever store the payinfo or CVV2 in the history....
- # You can see if it changed by the paymask...
- my $conf = new FS::Conf;
- if ($conf->exists('encryption') ) {
- @fields = grep $_ ne 'payinfo' && $_ ne 'cvv2', @fields;
- }
- my @values = map { _quote( $self->getfield($_), $self->table, $_) } @fields;
-
- "INSERT INTO h_". $self->table. " ( ".
- join(', ', qw(history_date history_user history_action), @fields ).
- ") VALUES (".
- join(', ', $time, dbh->quote(getotaker()), dbh->quote($action), @values).
- ")"
- ;
-}
-
-=item unique COLUMN
-
-B<Warning>: External use is B<deprecated>.
-
-Replaces COLUMN in record with a unique number, using counters in the
-filesystem. Used by the B<insert> method on single-field unique columns
-(see L<DBIx::DBSchema::Table>) and also as a fallback for primary keys
-that aren't SERIAL (Pg) or AUTO_INCREMENT (mysql).
-
-Returns the new value.
-
-=cut
-
-sub unique {
- my($self,$field) = @_;
- my($table)=$self->table;
-
- croak "Unique called on field $field, but it is ",
- $self->getfield($field),
- ", not null!"
- if $self->getfield($field);
-
- #warn "table $table is tainted" if is_tainted($table);
- #warn "field $field is tainted" if is_tainted($field);
-
- my($counter) = new File::CounterFile "$table.$field",0;
-# hack for web demo
-# getotaker() =~ /^([\w\-]{1,16})$/ or die "Illegal CGI REMOTE_USER!";
-# my($user)=$1;
-# my($counter) = new File::CounterFile "$user/$table.$field",0;
-# endhack
-
- my $index = $counter->inc;
- $index = $counter->inc while qsearchs($table, { $field=>$index } );
-
- $index =~ /^(\d*)$/;
- $index=$1;
-
- $self->setfield($field,$index);
-
-}
-
-=item ut_float COLUMN
-
-Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10. May not be
-null. If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_float {
- my($self,$field)=@_ ;
- ($self->getfield($field) =~ /^(\d+\.\d+)$/ ||
- $self->getfield($field) =~ /^(\d+)$/ ||
- $self->getfield($field) =~ /^(\d+\.\d+e\d+)$/ ||
- $self->getfield($field) =~ /^(\d+e\d+)$/)
- or return "Illegal or empty (float) $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-=item ut_floatn COLUMN
-
-Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10. May be
-null. If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-#false laziness w/ut_ipn
-sub ut_floatn {
- my( $self, $field ) = @_;
- if ( $self->getfield($field) =~ /^()$/ ) {
- $self->setfield($field,'');
- '';
- } else {
- $self->ut_float($field);
- }
-}
-
-=item ut_sfloat COLUMN
-
-Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10.
-May not be null. If there is an error, returns the error, otherwise returns
-false.
-
-=cut
-
-sub ut_sfloat {
- my($self,$field)=@_ ;
- ($self->getfield($field) =~ /^(-?\d+\.\d+)$/ ||
- $self->getfield($field) =~ /^(-?\d+)$/ ||
- $self->getfield($field) =~ /^(-?\d+\.\d+[eE]-?\d+)$/ ||
- $self->getfield($field) =~ /^(-?\d+[eE]-?\d+)$/)
- or return "Illegal or empty (float) $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-=item ut_sfloatn COLUMN
-
-Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10. May be
-null. If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_sfloatn {
- my( $self, $field ) = @_;
- if ( $self->getfield($field) =~ /^()$/ ) {
- $self->setfield($field,'');
- '';
- } else {
- $self->ut_sfloat($field);
- }
-}
-
-=item ut_snumber COLUMN
-
-Check/untaint signed numeric data (whole numbers). If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub ut_snumber {
- my($self, $field) = @_;
- $self->getfield($field) =~ /^(-?)\s*(\d+)$/
- or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
- $self->setfield($field, "$1$2");
- '';
-}
-
-=item ut_snumbern COLUMN
-
-Check/untaint signed numeric data (whole numbers). If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub ut_snumbern {
- my($self, $field) = @_;
- $self->getfield($field) =~ /^(-?)\s*(\d*)$/
- or return "Illegal (numeric) $field: ". $self->getfield($field);
- if ($1) {
- return "Illegal (numeric) $field: ". $self->getfield($field)
- unless $2;
- }
- $self->setfield($field, "$1$2");
- '';
-}
-
-=item ut_number COLUMN
-
-Check/untaint simple numeric data (whole numbers). May not be null. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_number {
- my($self,$field)=@_;
- $self->getfield($field) =~ /^(\d+)$/
- or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_numbern COLUMN
-
-Check/untaint simple numeric data (whole numbers). May be null. If there is
-an error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_numbern {
- my($self,$field)=@_;
- $self->getfield($field) =~ /^(\d*)$/
- or return "Illegal (numeric) $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_money COLUMN
-
-Check/untaint monetary numbers. May be negative. Set to 0 if null. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_money {
- my($self,$field)=@_;
- $self->setfield($field, 0) if $self->getfield($field) eq '';
- $self->getfield($field) =~ /^(\-)? ?(\d*)(\.\d{2})?$/
- or return "Illegal (money) $field: ". $self->getfield($field);
- #$self->setfield($field, "$1$2$3" || 0);
- $self->setfield($field, ( ($1||''). ($2||''). ($3||'') ) || 0);
- '';
-}
-
-=item ut_text COLUMN
-
-Check/untaint text. Alphanumerics, spaces, and the following punctuation
-symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ]
-May not be null. If there is an error, returns the error, otherwise returns
-false.
-
-=cut
-
-sub ut_text {
- my($self,$field)=@_;
- #warn "msgcat ". \&msgcat. "\n";
- #warn "notexist ". \&notexist. "\n";
- #warn "AUTOLOAD ". \&AUTOLOAD. "\n";
- $self->getfield($field)
- =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)$/
- or return gettext('illegal_or_empty_text'). " $field: ".
- $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_textn COLUMN
-
-Check/untaint text. Alphanumerics, spaces, and the following punctuation
-symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? /
-May be null. If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_textn {
- my($self,$field)=@_;
- $self->getfield($field)
- =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
- or return gettext('illegal_text'). " $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_alpha COLUMN
-
-Check/untaint alphanumeric strings (no spaces). May not be null. If there is
-an error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_alpha {
- my($self,$field)=@_;
- $self->getfield($field) =~ /^(\w+)$/
- or return "Illegal or empty (alphanumeric) $field: ".
- $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_alpha COLUMN
-
-Check/untaint alphanumeric strings (no spaces). May be null. If there is an
-error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_alphan {
- my($self,$field)=@_;
- $self->getfield($field) =~ /^(\w*)$/
- or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_alpha_lower COLUMN
-
-Check/untaint lowercase alphanumeric strings (no spaces). May not be null. If
-there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub ut_alpha_lower {
- my($self,$field)=@_;
- $self->getfield($field) =~ /[[:upper:]]/
- and return "Uppercase characters are not permitted in $field";
- $self->ut_alpha($field);
-}
-
-=item ut_phonen COLUMN [ COUNTRY ]
-
-Check/untaint phone numbers. May be null. If there is an error, returns
-the error, otherwise returns false.
-
-Takes an optional two-letter ISO country code; without it or with unsupported
-countries, ut_phonen simply calls ut_alphan.
-
-=cut
-
-sub ut_phonen {
- my( $self, $field, $country ) = @_;
- return $self->ut_alphan($field) unless defined $country;
- my $phonen = $self->getfield($field);
- if ( $phonen eq '' ) {
- $self->setfield($field,'');
- } elsif ( $country eq 'US' || $country eq 'CA' ) {
- $phonen =~ s/\D//g;
- $phonen =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/
- or return gettext('illegal_phone'). " $field: ". $self->getfield($field);
- $phonen = "$1-$2-$3";
- $phonen .= " x$4" if $4;
- $self->setfield($field,$phonen);
- } else {
- warn "warning: don't know how to check phone numbers for country $country";
- return $self->ut_textn($field);
- }
- '';
-}
-
-=item ut_hex COLUMN
-
-Check/untaint hexadecimal values.
-
-=cut
-
-sub ut_hex {
- my($self, $field) = @_;
- $self->getfield($field) =~ /^([\da-fA-F]+)$/
- or return "Illegal (hex) $field: ". $self->getfield($field);
- $self->setfield($field, uc($1));
- '';
-}
-
-=item ut_hexn COLUMN
-
-Check/untaint hexadecimal values. May be null.
-
-=cut
-
-sub ut_hexn {
- my($self, $field) = @_;
- $self->getfield($field) =~ /^([\da-fA-F]*)$/
- or return "Illegal (hex) $field: ". $self->getfield($field);
- $self->setfield($field, uc($1));
- '';
-}
-=item ut_ip COLUMN
-
-Check/untaint ip addresses. IPv4 only for now.
-
-=cut
-
-sub ut_ip {
- my( $self, $field ) = @_;
- $self->getfield($field) =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
- or return "Illegal (IP address) $field: ". $self->getfield($field);
- for ( $1, $2, $3, $4 ) { return "Illegal (IP address) $field" if $_ > 255; }
- $self->setfield($field, "$1.$2.$3.$4");
- '';
-}
-
-=item ut_ipn COLUMN
-
-Check/untaint ip addresses. IPv4 only for now. May be null.
-
-=cut
-
-sub ut_ipn {
- my( $self, $field ) = @_;
- if ( $self->getfield($field) =~ /^()$/ ) {
- $self->setfield($field,'');
- '';
- } else {
- $self->ut_ip($field);
- }
-}
-
-=item ut_coord COLUMN [ LOWER [ UPPER ] ]
-
-Check/untaint coordinates.
-Accepts the following forms:
-DDD.DDDDD
--DDD.DDDDD
-DDD MM.MMM
--DDD MM.MMM
-DDD MM SS
--DDD MM SS
-DDD MM MMM
--DDD MM MMM
-
-The "DDD MM SS" and "DDD MM MMM" are potentially ambiguous.
-The latter form (that is, the MMM are thousands of minutes) is
-assumed if the "MMM" is exactly three digits or two digits > 59.
-
-To be safe, just use the DDD.DDDDD form.
-
-If LOWER or UPPER are specified, then the coordinate is checked
-for lower and upper bounds, respectively.
-
-=cut
-
-sub ut_coord {
-
- my ($self, $field) = (shift, shift);
-
- my $lower = shift if scalar(@_);
- my $upper = shift if scalar(@_);
- my $coord = $self->getfield($field);
- my $neg = $coord =~ s/^(-)//;
-
- my ($d, $m, $s) = (0, 0, 0);
-
- if (
- (($d) = ($coord =~ /^(\s*\d{1,3}(?:\.\d+)?)\s*$/)) ||
- (($d, $m) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2}(?:\.\d+))\s*$/)) ||
- (($d, $m, $s) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2})\s+(\d{1,3})\s*$/))
- ) {
- $s = (((($s =~ /^\d{3}$/) or $s > 59) ? ($s / 1000) : ($s / 60)) / 60);
- $m = $m / 60;
- if ($m > 59) {
- return "Invalid (coordinate with minutes > 59) $field: "
- . $self->getfield($field);
- }
-
- $coord = ($neg ? -1 : 1) * sprintf('%.8f', $d + $m + $s);
-
- if (defined($lower) and ($coord < $lower)) {
- return "Invalid (coordinate < $lower) $field: "
- . $self->getfield($field);;
- }
-
- if (defined($upper) and ($coord > $upper)) {
- return "Invalid (coordinate > $upper) $field: "
- . $self->getfield($field);;
- }
-
- $self->setfield($field, $coord);
- return '';
- }
-
- return "Invalid (coordinate) $field: " . $self->getfield($field);
-
-}
-
-=item ut_coordn COLUMN [ LOWER [ UPPER ] ]
-
-Same as ut_coord, except optionally null.
-
-=cut
-
-sub ut_coordn {
-
- my ($self, $field) = (shift, shift);
-
- if ($self->getfield($field) =~ /^$/) {
- return '';
- } else {
- return $self->ut_coord($field, @_);
- }
-
-}
-
-
-=item ut_domain COLUMN
-
-Check/untaint host and domain names.
-
-=cut
-
-sub ut_domain {
- my( $self, $field ) = @_;
- #$self->getfield($field) =~/^(\w+\.)*\w+$/
- $self->getfield($field) =~/^(([\w\-]+\.)*\w+)$/
- or return "Illegal (domain) $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_name COLUMN
-
-Check/untaint proper names; allows alphanumerics, spaces and the following
-punctuation: , . - '
-
-May not be null.
-
-=cut
-
-sub ut_name {
- my( $self, $field ) = @_;
- $self->getfield($field) =~ /^([\w \,\.\-\']+)$/
- or return gettext('illegal_name'). " $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_zip COLUMN
-
-Check/untaint zip codes.
-
-=cut
-
-my @zip_reqd_countries = qw( AU CA US ); #CA, US implicit...
-
-sub ut_zip {
- my( $self, $field, $country ) = @_;
-
- if ( $country eq 'US' ) {
-
- $self->getfield($field) =~ /^\s*(\d{5}(\-\d{4})?)\s*$/
- or return gettext('illegal_zip'). " $field for country $country: ".
- $self->getfield($field);
- $self->setfield($field, $1);
-
- } elsif ( $country eq 'CA' ) {
-
- $self->getfield($field) =~ /^\s*([A-Z]\d[A-Z])\s*(\d[A-Z]\d)\s*$/i
- or return gettext('illegal_zip'). " $field for country $country: ".
- $self->getfield($field);
- $self->setfield($field, "$1 $2");
-
- } else {
-
- if ( $self->getfield($field) =~ /^\s*$/
- && ( !$country || ! grep { $_ eq $country } @zip_reqd_countries )
- )
- {
- $self->setfield($field,'');
- } else {
- $self->getfield($field) =~ /^\s*(\w[\w\-\s]{2,8}\w)\s*$/
- or return gettext('illegal_zip'). " $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- }
-
- }
-
- '';
-}
-
-=item ut_country COLUMN
-
-Check/untaint country codes. Country names are changed to codes, if possible -
-see L<Locale::Country>.
-
-=cut
-
-sub ut_country {
- my( $self, $field ) = @_;
- unless ( $self->getfield($field) =~ /^(\w\w)$/ ) {
- if ( $self->getfield($field) =~ /^([\w \,\.\(\)\']+)$/
- && country2code($1) ) {
- $self->setfield($field,uc(country2code($1)));
- }
- }
- $self->getfield($field) =~ /^(\w\w)$/
- or return "Illegal (country) $field: ". $self->getfield($field);
- $self->setfield($field,uc($1));
- '';
-}
-
-=item ut_anything COLUMN
-
-Untaints arbitrary data. Be careful.
-
-=cut
-
-sub ut_anything {
- my( $self, $field ) = @_;
- $self->getfield($field) =~ /^(.*)$/s
- or return "Illegal $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
-}
-
-=item ut_enum COLUMN CHOICES_ARRAYREF
-
-Check/untaint a column, supplying all possible choices, like the "enum" type.
-
-=cut
-
-sub ut_enum {
- my( $self, $field, $choices ) = @_;
- foreach my $choice ( @$choices ) {
- if ( $self->getfield($field) eq $choice ) {
- $self->setfield($choice);
- return '';
- }
- }
- return "Illegal (enum) field $field: ". $self->getfield($field);
-}
-
-=item ut_foreign_key COLUMN FOREIGN_TABLE FOREIGN_COLUMN
-
-Check/untaint a foreign column key. Call a regular ut_ method (like ut_number)
-on the column first.
-
-=cut
-
-sub ut_foreign_key {
- my( $self, $field, $table, $foreign ) = @_;
- qsearchs($table, { $foreign => $self->getfield($field) })
- or return "Can't find ". $self->table. ".$field ". $self->getfield($field).
- " in $table.$foreign";
- '';
-}
-
-=item ut_foreign_keyn COLUMN FOREIGN_TABLE FOREIGN_COLUMN
-
-Like ut_foreign_key, except the null value is also allowed.
-
-=cut
-
-sub ut_foreign_keyn {
- my( $self, $field, $table, $foreign ) = @_;
- $self->getfield($field)
- ? $self->ut_foreign_key($field, $table, $foreign)
- : '';
-}
-
-=item ut_agentnum_acl
-
-Checks this column as an agentnum, taking into account the current users's
-ACLs.
-
-=cut
-
-sub ut_agentnum_acl {
- my( $self, $field, $null_acl ) = @_;
-
- my $error = $self->ut_foreign_keyn($field, 'agent', 'agentnum');
- return "Illegal agentnum: $error" if $error;
-
- my $curuser = $FS::CurrentUser::CurrentUser;
-
- if ( $self->$field() ) {
-
- return "Access deined"
- unless $curuser->agentnum($self->$field());
-
- } else {
-
- return "Access denied"
- unless $curuser->access_right($null_acl);
-
- }
-
- '';
-
-}
-
-=item virtual_fields [ TABLE ]
-
-Returns a list of virtual fields defined for the table. This should not
-be exported, and should only be called as an instance or class method.
-
-=cut
-
-sub virtual_fields {
- my $self = shift;
- my $table;
- $table = $self->table or confess "virtual_fields called on non-table";
-
- confess "Unknown table $table" unless dbdef->table($table);
-
- return () unless dbdef->table('part_virtual_field');
-
- unless ( $virtual_fields_cache{$table} ) {
- my $query = 'SELECT name from part_virtual_field ' .
- "WHERE dbtable = '$table'";
- my $dbh = dbh;
- my $result = $dbh->selectcol_arrayref($query);
- confess "Error executing virtual fields query: $query: ". $dbh->errstr
- if $dbh->err;
- $virtual_fields_cache{$table} = $result;
- }
-
- @{$virtual_fields_cache{$table}};
-
-}
-
-
-=item fields [ TABLE ]
-
-This is a wrapper for real_fields and virtual_fields. Code that called
-fields before should probably continue to call fields.
-
-=cut
-
-sub fields {
- my $something = shift;
- my $table;
- if($something->isa('FS::Record')) {
- $table = $something->table;
- } else {
- $table = $something;
- $something = "FS::$table";
- }
- return (real_fields($table), $something->virtual_fields());
-}
-
-=item pvf FIELD_NAME
-
-Returns the FS::part_virtual_field object corresponding to a field in the
-record (specified by FIELD_NAME).
-
-=cut
-
-sub pvf {
- my ($self, $name) = (shift, shift);
-
- if(grep /^$name$/, $self->virtual_fields) {
- return qsearchs('part_virtual_field', { dbtable => $self->table,
- name => $name } );
- }
- ''
-}
-
-=item vfieldpart_hashref TABLE
-
-Returns a hashref of virtual field names and vfieldparts applicable to the given
-TABLE.
-
-=cut
-
-sub vfieldpart_hashref {
- my $self = shift;
- my $table = $self->table;
-
- return {} unless dbdef->table('part_virtual_field');
-
- my $dbh = dbh;
- my $statement = "SELECT vfieldpart, name FROM part_virtual_field WHERE ".
- "dbtable = '$table'";
- my $sth = $dbh->prepare($statement);
- $sth->execute or croak "Execution of '$statement' failed: ".$dbh->errstr;
- return { map { $_->{name}, $_->{vfieldpart} }
- @{$sth->fetchall_arrayref({})} };
-
-}
-
-=item encrypt($value)
-
-Encrypts the credit card using a combination of PK to encrypt and uuencode to armour.
-
-Returns the encrypted string.
-
-You should generally not have to worry about calling this, as the system handles this for you.
-
-=cut
-
-sub encrypt {
- my ($self, $value) = @_;
- my $encrypted;
-
- my $conf = new FS::Conf;
- if ($conf->exists('encryption')) {
- if ($self->is_encrypted($value)) {
- # Return the original value if it isn't plaintext.
- $encrypted = $value;
- } else {
- $self->loadRSA;
- if (ref($rsa_encrypt) =~ /::RSA/) { # We Can Encrypt
- # RSA doesn't like the empty string so let's pack it up
- # The database doesn't like the RSA data so uuencode it
- my $length = length($value)+1;
- $encrypted = pack("u*",$rsa_encrypt->encrypt(pack("Z$length",$value)));
- } else {
- die ("You can't encrypt w/o a valid RSA engine - Check your installation or disable encryption");
- }
- }
- }
- return $encrypted;
-}
-
-=item is_encrypted($value)
-
-Checks to see if the string is encrypted and returns true or false (1/0) to indicate it's status.
-
-=cut
-
-
-sub is_encrypted {
- my ($self, $value) = @_;
- # Possible Bug - Some work may be required here....
-
- if ($value =~ /^M/ && length($value) > 80) {
- return 1;
- } else {
- return 0;
- }
-}
-
-=item decrypt($value)
-
-Uses the private key to decrypt the string. Returns the decryoted string or undef on failure.
-
-You should generally not have to worry about calling this, as the system handles this for you.
-
-=cut
-
-sub decrypt {
- my ($self,$value) = @_;
- my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
- my $conf = new FS::Conf;
- if ($conf->exists('encryption') && $self->is_encrypted($value)) {
- $self->loadRSA;
- if (ref($rsa_decrypt) =~ /::RSA/) {
- my $encrypted = unpack ("u*", $value);
- $decrypted = unpack("Z*", eval{$rsa_decrypt->decrypt($encrypted)});
- if ($@) {warn "Decryption Failed"};
- }
- }
- return $decrypted;
-}
-
-sub loadRSA {
- my $self = shift;
- #Initialize the Module
- $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default
-
- my $conf = new FS::Conf;
- if ($conf->exists('encryptionmodule') && $conf->config_binary('encryptionmodule') ne '') {
- $rsa_module = $conf->config('encryptionmodule');
- }
-
- if (!$rsa_loaded) {
- eval ("require $rsa_module"); # No need to import the namespace
- $rsa_loaded++;
- }
- # Initialize Encryption
- if ($conf->exists('encryptionpublickey') && $conf->config_binary('encryptionpublickey') ne '') {
- my $public_key = join("\n",$conf->config('encryptionpublickey'));
- $rsa_encrypt = $rsa_module->new_public_key($public_key);
- }
-
- # Intitalize Decryption
- if ($conf->exists('encryptionprivatekey') && $conf->config_binary('encryptionprivatekey') ne '') {
- my $private_key = join("\n",$conf->config('encryptionprivatekey'));
- $rsa_decrypt = $rsa_module->new_private_key($private_key);
- }
-}
-
-=item h_search ACTION
-
-Given an ACTION, either "insert", or "delete", returns the appropriate history
-record corresponding to this record, if any.
-
-=cut
-
-sub h_search {
- my( $self, $action ) = @_;
-
- my $table = $self->table;
- $table =~ s/^h_//;
-
- my $primary_key = dbdef->table($table)->primary_key;
-
- qsearchs({
- 'table' => "h_$table",
- 'hashref' => { $primary_key => $self->$primary_key(),
- 'history_action' => $action,
- },
- });
-
-}
-
-=item h_date ACTION
-
-Given an ACTION, either "insert", or "delete", returns the timestamp of the
-appropriate history record corresponding to this record, if any.
-
-=cut
-
-sub h_date {
- my($self, $action) = @_;
- my $h = $self->h_search($action);
- $h ? $h->history_date : '';
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item real_fields [ TABLE ]
-
-Returns a list of the real columns in the specified table. Called only by
-fields() and other subroutines elsewhere in FS::Record.
-
-=cut
-
-sub real_fields {
- my $table = shift;
-
- my($table_obj) = dbdef->table($table);
- confess "Unknown table $table" unless $table_obj;
- $table_obj->columns;
-}
-
-=item _quote VALUE, TABLE, COLUMN
-
-This is an internal function used to construct SQL statements. It returns
-VALUE DBI-quoted (see L<DBI/"quote">) unless VALUE is a number and the column
-type (see L<DBIx::DBSchema::Column>) does not end in `char' or `binary'.
-
-=cut
-
-sub _quote {
- my($value, $table, $column) = @_;
- my $column_obj = dbdef->table($table)->column($column);
- my $column_type = $column_obj->type;
- my $nullable = $column_obj->null;
-
- warn " $table.$column: $value ($column_type".
- ( $nullable ? ' NULL' : ' NOT NULL' ).
- ")\n" if $DEBUG > 2;
-
- if ( $value eq '' && $nullable ) {
- 'NULL'
- } elsif ( $value eq '' && $column_type =~ /^(int|numeric)/ ) {
- cluck "WARNING: Attempting to set non-null integer $table.$column null; ".
- "using 0 instead";
- 0;
- } elsif ( $value =~ /^\d+(\.\d+)?$/ &&
- ! $column_type =~ /(char|binary|text)$/i ) {
- $value;
- } else {
- dbh->quote($value);
- }
-}
-
-=item hfields TABLE
-
-This is deprecated. Don't use it.
-
-It returns a hash-type list with the fields of this record's table set true.
-
-=cut
-
-sub hfields {
- carp "warning: hfields is deprecated";
- my($table)=@_;
- my(%hash);
- foreach (fields($table)) {
- $hash{$_}=1;
- }
- \%hash;
-}
-
-sub _dump {
- my($self)=@_;
- join("\n", map {
- "$_: ". $self->getfield($_). "|"
- } (fields($self->table)) );
-}
-
-sub DESTROY { return; }
-
-#sub DESTROY {
-# my $self = shift;
-# #use Carp qw(cluck);
-# #cluck "DESTROYING $self";
-# warn "DESTROYING $self";
-#}
-
-#sub is_tainted {
-# return ! eval { join('',@_), kill 0; 1; };
-# }
-
-=item str2time_sql [ DRIVER_NAME ]
-
-Returns a function to convert to unix time based on database type, such as
-"EXTRACT( EPOCH FROM" for Pg or "UNIX_TIMESTAMP(" for mysql. See
-the str2time_sql_closing method to return a closing string rather than just
-using a closing parenthesis as previously suggested.
-
-You can pass an optional driver name such as "Pg", "mysql" or
-$dbh->{Driver}->{Name} to return a function for that database instead of
-the current database.
-
-=cut
-
-sub str2time_sql {
- my $driver = shift || driver_name;
-
- return 'UNIX_TIMESTAMP(' if $driver =~ /^mysql/i;
- return 'EXTRACT( EPOCH FROM ' if $driver =~ /^Pg/i;
-
- warn "warning: unknown database type $driver; guessing how to convert ".
- "dates to UNIX timestamps";
- return 'EXTRACT(EPOCH FROM ';
-
-}
-
-=item str2time_sql_closing [ DRIVER_NAME ]
-
-Returns the closing suffix of a function to convert to unix time based on
-database type, such as ")::integer" for Pg or ")" for mysql.
-
-You can pass an optional driver name such as "Pg", "mysql" or
-$dbh->{Driver}->{Name} to return a function for that database instead of
-the current database.
-
-=cut
-
-sub str2time_sql_closing {
- my $driver = shift || driver_name;
-
- return ' )::INTEGER ' if $driver =~ /^Pg/i;
- return ' ) ';
-}
-
-=back
-
-=head1 BUGS
-
-This module should probably be renamed, since much of the functionality is
-of general use. It is not completely unlike Adapter::DBI (see below).
-
-Exported qsearch and qsearchs should be deprecated in favor of method calls
-(against an FS::Record object like the old search and searchs that qsearch
-and qsearchs were on top of.)
-
-The whole fields / hfields mess should be removed.
-
-The various WHERE clauses should be subroutined.
-
-table string should be deprecated in favor of DBIx::DBSchema::Table.
-
-No doubt we could benefit from a Tied hash. Documenting how exists / defined
-true maps to the database (and WHERE clauses) would also help.
-
-The ut_ methods should ask the dbdef for a default length.
-
-ut_sqltype (like ut_varchar) should all be defined
-
-A fallback check method should be provided which uses the dbdef.
-
-The ut_money method assumes money has two decimal digits.
-
-The Pg money kludge in the new method only strips `$'.
-
-The ut_phonen method only checks US-style phone numbers.
-
-The _quote function should probably use ut_float instead of a regex.
-
-All the subroutines probably should be methods, here or elsewhere.
-
-Probably should borrow/use some dbdef methods where appropriate (like sub
-fields)
-
-As of 1.14, DBI fetchall_hashref( {} ) doesn't set fetchrow_hashref NAME_lc,
-or allow it to be set. Working around it is ugly any way around - DBI should
-be fixed. (only affects RDBMS which return uppercase column names)
-
-ut_zip should take an optional country like ut_phone.
-
-=head1 SEE ALSO
-
-L<DBIx::DBSchema>, L<FS::UID>, L<DBI>
-
-Adapter::DBI from Ch. 11 of Advanced Perl Programming by Sriram Srinivasan.
-
-http://poop.sf.net/
-
-=cut
-
-1;
-
diff --git a/FS/FS/Report.pm b/FS/FS/Report.pm
deleted file mode 100644
index 181fea2..0000000
--- a/FS/FS/Report.pm
+++ /dev/null
@@ -1,46 +0,0 @@
-package FS::Report;
-
-use strict;
-
-=head1 NAME
-
-FS::Report - Report data objects
-
-=head1 SYNOPSIS
-
- #see the more speicific report objects, currently only FS::Report::Table
-
-=head1 DESCRIPTION
-
-See the more specific report objects, currently only FS::Report::Table
-
-=head1 METHODS
-
-=over 4
-
-=item new [ OPTION => VALUE ... ]
-
-Constructor. Takes a list of options and their values.
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = @_ ? ( ref($_[0]) ? shift : { @_ } ) : {};
- bless( $self, $class );
-}
-
-=back
-
-=head1 BUGS
-
-Documentation.
-
-=head1 SEE ALSO
-
-L<FS::Report::Table>, reports in the web interface.
-
-=cut
-
-1;
diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm
deleted file mode 100644
index 9f636fa..0000000
--- a/FS/FS/Report/Table.pm
+++ /dev/null
@@ -1,27 +0,0 @@
-package FS::Report::Table;
-
-use strict;
-use vars qw( @ISA );
-use FS::Report;
-
-@ISA = qw( FS::Report );
-
-=head1 NAME
-
-FS::Report::Table - Tables of report data
-
-=head1 SYNOPSIS
-
-See the more specific report objects, currently only FS::Report::Table::Monthly
-
-=head1 BUGS
-
-Documentation.
-
-=head1 SEE ALSO
-
-L<FS::Report::Table::Monthly>, reports in the web interface.
-
-=cut
-
-1;
diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm
deleted file mode 100644
index 7463620..0000000
--- a/FS/FS/Report/Table/Monthly.pm
+++ /dev/null
@@ -1,378 +0,0 @@
-package FS::Report::Table::Monthly;
-
-use strict;
-use vars qw( @ISA $expenses_kludge );
-use Time::Local;
-use FS::UID qw( dbh );
-use FS::Report::Table;
-use FS::CurrentUser;
-
-@ISA = qw( FS::Report::Table );
-
-$expenses_kludge = 0;
-
-=head1 NAME
-
-FS::Report::Table::Monthly - Tables of report data, indexed monthly
-
-=head1 SYNOPSIS
-
- use FS::Report::Table::Monthly;
-
- my $report = new FS::Report::Table::Monthly (
- 'items' => [ 'invoiced', 'netsales', 'credits', 'receipts', ],
- 'start_month' => 4,
- 'start_year' => 2000,
- 'end_month' => 4,
- 'end_year' => 2020,
- #opt
- 'agentnum' => 54
- 'params' => [ [ 'paramsfor', 'item_one' ], [ 'item', 'two' ] ], # ...
- 'remove_empty' => 1, #collapse empty rows, default 0
- 'item_labels' => [ ], #useful with remove_empty
- );
-
- my $data = $report->data;
-
-=head1 METHODS
-
-=over 4
-
-=item data
-
-Returns a hashref of data (!! describe)
-
-=cut
-
-sub data {
- my $self = shift;
-
- #use Data::Dumper;
- #warn Dumper($self);
-
- my $smonth = $self->{'start_month'};
- my $syear = $self->{'start_year'};
- my $emonth = $self->{'end_month'};
- my $eyear = $self->{'end_year'};
- my $agentnum = $self->{'agentnum'};
-
- my %data;
-
- while ( $syear < $eyear || ( $syear == $eyear && $smonth < $emonth+1 ) ) {
-
- push @{$data{label}}, "$smonth/$syear";
-
- my $speriod = timelocal(0,0,0,1,$smonth-1,$syear);
- push @{$data{speriod}}, $speriod;
- if ( ++$smonth == 13 ) { $syear++; $smonth=1; }
- my $eperiod = timelocal(0,0,0,1,$smonth-1,$syear);
- push @{$data{eperiod}}, $eperiod;
-
- my $col = 0;
- my @row = ();
- foreach my $item ( @{$self->{'items'}} ) {
- my @param = $self->{'params'} ? @{ $self->{'params'}[$col] }: ();
- my $value = $self->$item($speriod, $eperiod, $agentnum, @param);
- #push @{$data{$item}}, $value;
- push @{$data{data}->[$col++]}, $value;
- }
-
- }
-
- #these need to get generalized, sheesh
- $data{'items'} = $self->{'items'};
- $data{'item_labels'} = $self->{'item_labels'} || $self->{'items'};
- $data{'colors'} = $self->{'colors'};
- $data{'links'} = $self->{'links'} || [];
-
- #use Data::Dumper;
- #warn Dumper(\%data);
-
- if ( $self->{'remove_empty'} ) {
-
- #warn "removing empty rows\n";
-
- my $col = 0;
- #these need to get generalized, sheesh
- my @newitems = ();
- my @newlabels = ();
- my @newdata = ();
- my @newcolors = ();
- my @newlinks = ();
- foreach my $item ( @{$self->{'items'}} ) {
-
- if ( grep { $_ != 0 } @{$data{'data'}->[$col]} ) {
- push @newitems, $data{'items'}->[$col];
- push @newlabels, $data{'item_labels'}->[$col];
- push @newdata, $data{'data'}->[$col];
- push @newcolors, $data{'colors'}->[$col];
- push @newlinks, $data{'links'}->[$col];
- }
-
- $col++;
- }
-
- $data{'items'} = \@newitems;
- $data{'item_labels'} = \@newlabels;
- $data{'data'} = \@newdata;
- $data{'colors'} = \@newcolors;
- $data{'links'} = \@newlinks;
-
- }
-
- #use Data::Dumper;
- #warn Dumper(\%data);
-
- \%data;
-
-}
-
-sub invoiced { #invoiced
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
-
- $self->scalar_sql("
- SELECT SUM(charged)
- FROM cust_bill
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- );
-
-}
-
-sub netsales { #net sales
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
-
- my $credited = $self->scalar_sql("
- SELECT SUM(cust_credit_bill.amount)
- FROM cust_credit_bill
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- 'cust_bill._date'
- )
- );
-
- #horrible local kludge
- my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql("
- SELECT SUM(cust_bill_pkg.setup)
- FROM cust_bill_pkg
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- LEFT JOIN cust_pkg USING ( pkgnum )
- LEFT JOIN part_pkg USING ( pkgpart )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- 'cust_bill._date'
- ). "
- AND LOWER(part_pkg.pkg) LIKE 'expense _%'
- ");
-
- $self->invoiced($speriod,$eperiod,$agentnum) - $credited - $expenses;
-}
-
-#deferred revenue
-
-sub receipts { #cashflow
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
-
- my $refunded = $self->scalar_sql("
- SELECT SUM(refund)
- FROM cust_refund
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- );
-
- #horrible local kludge that doesn't even really work right
- my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql("
- SELECT SUM(cust_bill_pay.amount)
- FROM cust_bill_pay
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- 'cust_bill_pay._date'
- ). "
- AND 0 < ( SELECT COUNT(*) from cust_bill_pkg, cust_pkg, part_pkg
- WHERE cust_bill.invnum = cust_bill_pkg.invnum
- AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum
- AND cust_pkg.pkgpart = part_pkg.pkgpart
- AND LOWER(part_pkg.pkg) LIKE 'expense _%'
- )
- ");
- # my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'";
-
- $self->payments($speriod, $eperiod, $agentnum) - $refunded - $expenses;
-}
-
-sub payments {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $self->scalar_sql("
- SELECT SUM(paid)
- FROM cust_pay
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- );
-}
-
-sub credits {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $self->scalar_sql("
- SELECT SUM(amount)
- FROM cust_credit
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- );
-}
-
-sub netcredits {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $self->scalar_sql("
- SELECT SUM(cust_credit_bill.amount)
- FROM cust_credit_bill
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- 'cust_bill._date'
- )
- );
-}
-
-#these should be auto-generated or $AUTOLOADed or something
-sub invoiced_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->invoiced($speriod, $eperiod, $agentnum);
-}
-
-sub netsales_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->netsales($speriod, $eperiod, $agentnum);
-}
-
-sub receipts_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->receipts($speriod, $eperiod, $agentnum);
-}
-
-sub payments_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->payments($speriod, $eperiod, $agentnum);
-}
-
-sub credits_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->credits($speriod, $eperiod, $agentnum);
-}
-
-#not being too bad with the false laziness
-use Time::Local qw(timelocal);
-sub _subtract_11mo {
- my($self, $time) = @_;
- my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5];
- $mon -= 11;
- if ( $mon < 0 ) { $mon+=12; $year--; }
- timelocal($sec,$min,$hour,$mday,$mon,$year);
-}
-
-sub cust_bill_pkg {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
-
- my $where = '';
- if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
- if ( $1 == 0 ) {
- $where = "classnum IS NULL";
- } else {
- $where = "classnum = $1";
- }
- }
-
- $agentnum ||= $opt{'agentnum'};
-
- $self->scalar_sql("
- SELECT SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)
- FROM cust_bill_pkg
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- LEFT JOIN cust_pkg USING ( pkgnum )
- LEFT JOIN part_pkg USING ( pkgpart )
- WHERE pkgnum != 0
- AND $where
- AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- );
-
-}
-
-sub setup_pkg { shift->pkg_field( @_, 'setup' ); }
-sub susp_pkg { shift->pkg_field( @_, 'susp' ); }
-sub cancel_pkg { shift->pkg_field( @_, 'cancel'); }
-
-sub pkg_field {
- my( $self, $speriod, $eperiod, $agentnum, $field ) = @_;
- $self->scalar_sql("
- SELECT COUNT(*) FROM cust_pkg
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- "cust_pkg.$field",
- )
- );
-
-}
-
-#this is going to be harder..
-#sub unsusp_pkg {
-# my( $self, $speriod, $eperiod, $agentnum ) = @_;
-# $self->scalar_sql("
-# SELECT COUNT(*) FROM h_cust_pkg
-# WHERE
-#
-#}
-
-sub in_time_period_and_agent {
- my( $self, $speriod, $eperiod, $agentnum ) = splice(@_, 0, 4);
- my $col = @_ ? shift() : '_date';
-
- my $sql = "$col >= $speriod AND $col < $eperiod";
-
- #agent selection
- $sql .= " AND cust_main.agentnum = $agentnum"
- if $agentnum;
-
- #agent virtualization
- $sql .= ' AND '.
- $FS::CurrentUser::CurrentUser->agentnums_sql( 'table'=>'cust_main' );
-
- $sql;
-}
-
-sub scalar_sql {
- my( $self, $sql ) = ( shift, shift );
- my $sth = dbh->prepare($sql) or die dbh->errstr;
- $sth->execute
- or die "Unexpected error executing statement $sql: ". $sth->errstr;
- $sth->fetchrow_arrayref->[0] || 0;
-}
-
-=back
-
-=head1 BUGS
-
-Documentation.
-
-=head1 SEE ALSO
-
-=cut
-
-1;
-
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
deleted file mode 100644
index 9548aa7..0000000
--- a/FS/FS/Schema.pm
+++ /dev/null
@@ -1,1962 +0,0 @@
-package FS::Schema;
-
-use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache);
-use subs qw(reload_dbdef);
-use Exporter;
-use DBIx::DBSchema 0.33;
-use DBIx::DBSchema::Table;
-use DBIx::DBSchema::Column 0.06;
-use DBIx::DBSchema::Index;
-
-@ISA = qw(Exporter);
-@EXPORT_OK = qw( dbdef dbdef_dist reload_dbdef );
-
-$DEBUG = 0;
-$me = '[FS::Schema]';
-
-=head1 NAME
-
-FS::Schema - Freeside database schema
-
-=head1 SYNOPSYS
-
- use FS::Schema qw(dbdef dbdef_dist reload_dbdef);
-
- $dbdef = reload_dbdef;
- $dbdef = reload_dbdef "/non/standard/filename";
- $dbdef = dbdef;
- $dbdef_dist = dbdef_dist;
-
-=head1 DESCRIPTION
-
-This class represents the database schema.
-
-=head1 METHODS
-
-=over 4
-
-=item reload_dbdef([FILENAME])
-
-Load a database definition (see L<DBIx::DBSchema>), optionally from a
-non-default filename. This command is executed at startup unless
-I<$FS::Schema::setup_hack> is true. Returns a DBIx::DBSchema object.
-
-=cut
-
-sub reload_dbdef {
- my $file = shift;
-
- unless ( exists $dbdef_cache{$file} ) {
- warn "[debug]$me loading dbdef for $file\n" if $DEBUG;
- $dbdef_cache{$file} = DBIx::DBSchema->load( $file )
- or die "can't load database schema from $file: $DBIx::DBSchema::errstr\n";
- } else {
- warn "[debug]$me re-using cached dbdef for $file\n" if $DEBUG;
- }
- $dbdef = $dbdef_cache{$file};
-}
-
-=item dbdef
-
-Returns the current database definition (represents the current database,
-assuming it is up-to-date). See L<DBIx::DBSchema>.
-
-=cut
-
-sub dbdef { $dbdef; }
-
-=item dbdef_dist [ DATASRC ]
-
-Returns the current canoical database definition as defined in this file.
-
-Optionally, pass a DBI data source to enable syntax specific to that database.
-Currently, this enables "TYPE=InnoDB" for MySQL databases.
-
-=cut
-
-sub dbdef_dist {
- my $datasrc = @_ ? shift : '';
-
- my $local_options = '';
- if ( $datasrc =~ /^dbi:mysql/i ) {
- $local_options = 'TYPE=InnoDB';
- }
-
- ###
- # create a dbdef object from the old data structure
- ###
-
- my $tables_hashref = tables_hashref();
-
- #turn it into objects
- my $dbdef = new DBIx::DBSchema map {
-
- my $tablename = $_;
- my $indexnum = 1;
-
- my @columns;
- while (@{$tables_hashref->{$tablename}{'columns'}}) {
- #my($name, $type, $null, $length, $default, $local) =
- my @coldef =
- splice @{$tables_hashref->{$tablename}{'columns'}}, 0, 6;
- my %hash = map { $_ => shift @coldef }
- qw( name type null length default local );
-
- unless ( defined $hash{'default'} ) {
- warn "$tablename:\n".
- join('', map "$_ => $hash{$_}\n", keys %hash) ;# $stop = <STDIN>;
- }
-
- push @columns, new DBIx::DBSchema::Column ( \%hash );
- }
-
- #false laziness w/sub indices in DBIx::DBSchema::DBD (well, sorta)
- #and sub sql_create_table in DBIx::DBSchema::Table (slighty more?)
- my $unique = $tables_hashref->{$tablename}{'unique'};
- my $index = $tables_hashref->{$tablename}{'index'};
- my @indices = ();
- push @indices, map {
- DBIx::DBSchema::Index->new({
- 'name' => $tablename. $indexnum++,
- 'unique' => 1,
- 'columns' => $_,
- });
- }
- @$unique;
- push @indices, map {
- DBIx::DBSchema::Index->new({
- 'name' => $tablename. $indexnum++,
- 'unique' => 0,
- 'columns' => $_,
- });
- }
- @$index;
-
- DBIx::DBSchema::Table->new({
- 'name' => $tablename,
- 'primary_key' => $tables_hashref->{$tablename}{'primary_key'},
- 'columns' => \@columns,
- 'indices' => \@indices,
- 'local_options' => $local_options,
- });
-
- } keys %$tables_hashref;
-
- if ( $DEBUG ) {
- warn "[debug]$me initial dbdef_dist created ($dbdef) with tables:\n";
- warn "[debug]$me $_\n" foreach $dbdef->tables;
- }
-
- #add radius attributes to svc_acct
- #
- #my($svc_acct)=$dbdef->table('svc_acct');
- #
- #my($attribute);
- #foreach $attribute (@attributes) {
- # $svc_acct->addcolumn ( new DBIx::DBSchema::Column (
- # 'radius_'. $attribute,
- # 'varchar',
- # 'NULL',
- # $char_d,
- # ));
- #}
- #
- #foreach $attribute (@check_attributes) {
- # $svc_acct->addcolumn( new DBIx::DBSchema::Column (
- # 'rc_'. $attribute,
- # 'varchar',
- # 'NULL',
- # $char_d,
- # ));
- #}
-
- #create history tables (false laziness w/create-history-tables)
- foreach my $table (
- grep { ! /^clientapi_session/ }
- grep { ! /^h_/ }
- $dbdef->tables
- ) {
- my $tableobj = $dbdef->table($table)
- or die "unknown table $table";
-
- my %indices = $tableobj->indices;
-
- my %h_indices = map {
- ( "h_$_" =>
- DBIx::DBSchema::Index->new({
- 'name' => 'h_'. $indices{$_}->name,
- 'unique' => 0,
- 'columns' => [ @{$indices{$_}->columns} ],
- })
- );
- }
- keys %indices;
-
- my $h_tableobj = DBIx::DBSchema::Table->new( {
- 'name' => "h_$table",
- 'primary_key' => 'historynum',
- 'indices' => \%h_indices,
- 'local_options' => $local_options,
- 'columns' => [
- DBIx::DBSchema::Column->new( {
- 'name' => 'historynum',
- 'type' => 'serial',
- 'null' => 'NOT NULL',
- 'length' => '',
- 'default' => '',
- 'local' => '',
- } ),
- DBIx::DBSchema::Column->new( {
- 'name' => 'history_date',
- 'type' => 'int',
- 'null' => 'NULL',
- 'length' => '',
- 'default' => '',
- 'local' => '',
- } ),
- DBIx::DBSchema::Column->new( {
- 'name' => 'history_user',
- 'type' => 'varchar',
- 'null' => 'NOT NULL',
- 'length' => '80',
- 'default' => '',
- 'local' => '',
- } ),
- DBIx::DBSchema::Column->new( {
- 'name' => 'history_action',
- 'type' => 'varchar',
- 'null' => 'NOT NULL',
- 'length' => '80',
- 'default' => '',
- 'local' => '',
- } ),
- map {
- my $column = $tableobj->column($_);
-
- #clone so as to not disturb the original
- $column = DBIx::DBSchema::Column->new( {
- map { $_ => $column->$_() }
- qw( name type null length default local )
- } );
-
- if ( $column->type =~ /^(\w*)SERIAL$/i ) {
- $column->type('int');
- $column->null('NULL');
- }
- #$column->default('')
- # if $column->default =~ /^nextval\(/i;
- #( my $local = $column->local ) =~ s/AUTO_INCREMENT//i;
- #$column->local($local);
- $column;
- } $tableobj->columns
- ],
- } );
- $dbdef->addtable($h_tableobj);
- }
-
- if ( $datasrc =~ /^dbi:mysql/i ) {
-
- my $dup_lock_table = DBIx::DBSchema::Table->new( {
- 'name' => 'duplicate_lock',
- 'primary_key' => 'duplocknum',
- 'local_options' => $local_options,
- 'columns' => [
- DBIx::DBSchema::Column->new( {
- 'name' => 'duplocknum',
- 'type' => 'serial',
- 'null' => 'NOT NULL',
- 'length' => '',
- 'default' => '',
- 'local' => '',
- } ),
- DBIx::DBSchema::Column->new( {
- 'name' => 'lockname',
- 'type' => 'varchar',
- 'null' => 'NOT NULL',
- 'length' => '80',
- 'default' => '',
- 'local' => '',
- } ),
- ],
- 'indices' => { 'duplicate_lock1' =>
- DBIx::DBSchema::Index->new({
- 'name' => 'duplicate_lock1',
- 'unique' => 1,
- 'columns' => [ 'lockname' ],
- })
- },
- } );
-
- $dbdef->addtable($dup_lock_table);
-
- }
-
- $dbdef;
-
-}
-
-sub tables_hashref {
-
- my $char_d = 80; #default maxlength for text fields
-
- #my(@date_type) = ( 'timestamp', '', '' );
- my @date_type = ( 'int', 'NULL', '' );
- my @perl_type = ( 'text', 'NULL', '' );
- my @money_type = ( 'decimal', '', '10,2' );
-
- my $username_len = 32; #usernamemax config file
-
- # name type nullability length default local
-
- return {
-
- 'agent' => {
- 'columns' => [
- 'agentnum', 'serial', '', '', '', '',
- 'agent', 'varchar', '', $char_d, '', '',
- 'typenum', 'int', '', '', '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- 'ticketing_queueid', 'int', 'NULL', '', '', '',
- 'invoice_template', 'varchar', 'NULL', $char_d, '', '',
- 'username', 'varchar', 'NULL', $char_d, '', '', #deprecated
- '_password', 'varchar', 'NULL', $char_d, '', '', #deprecated
- 'freq', 'int', 'NULL', '', '', '', #deprecated (never used)
- 'prog', @perl_type, '', '', #deprecated (never used)
-
- ],
- 'primary_key' => 'agentnum',
- 'unique' => [],
- 'index' => [ ['typenum'], ['disabled'] ],
- },
-
- 'agent_type' => {
- 'columns' => [
- 'typenum', 'serial', '', '', '', '',
- 'atype', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'typenum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'type_pkgs' => {
- 'columns' => [
- 'typepkgnum', 'serial', '', '', '', '',
- 'typenum', 'int', '', '', '', '',
- 'pkgpart', 'int', '', '', '', '',
- ],
- 'primary_key' => 'typepkgnum',
- 'unique' => [ ['typenum', 'pkgpart'] ],
- 'index' => [ ['typenum'] ],
- },
-
- 'cust_bill' => {
- 'columns' => [
- 'invnum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'charged', @money_type, '', '',
- 'printed', 'int', '', '', '', '',
- 'closed', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'invnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['_date'] ],
- },
-
- 'cust_bill_event' => {
- 'columns' => [
- 'eventnum', 'serial', '', '', '', '',
- 'invnum', 'int', '', '', '', '',
- 'eventpart', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'status', 'varchar', '', $char_d, '', '',
- 'statustext', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'eventnum',
- #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
- 'unique' => [],
- 'index' => [ ['invnum'], ['status'] ],
- },
-
- 'part_bill_event' => {
- 'columns' => [
- 'eventpart', 'serial', '', '', '', '',
- 'freq', 'varchar', 'NULL', $char_d, '', '',
- 'payby', 'char', '', 4, '', '',
- 'event', 'varchar', '', $char_d, '', '',
- 'eventcode', @perl_type, '', '',
- 'seconds', 'int', 'NULL', '', '', '',
- 'weight', 'int', '', '', '', '',
- 'plan', 'varchar', 'NULL', $char_d, '', '',
- 'plandata', 'text', 'NULL', '', '', '',
- 'reason', 'int', 'NULL', '', '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'eventpart',
- 'unique' => [],
- 'index' => [ ['payby'], ['disabled'], ],
- },
-
- 'part_event' => {
- 'columns' => [
- 'eventpart', 'serial', '', '', '', '',
- 'agentnum', 'int', 'NULL', '', '', '',
- 'event', 'varchar', '', $char_d, '', '',
- 'eventtable', 'varchar', '', $char_d, '', '',
- 'check_freq', 'varchar', 'NULL', $char_d, '', '',
- 'weight', 'int', '', '', '', '',
- 'action', 'varchar', '', $char_d, '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'eventpart',
- 'unique' => [],
- 'index' => [ ['agentnum'], ['eventtable'], ['check_freq'], ['disabled'], ],
- },
-
- 'part_event_option' => {
- 'columns' => [
- 'optionnum', 'serial', '', '', '', '',
- 'eventpart', 'int', '', '', '', '',
- 'optionname', 'varchar', '', $char_d, '', '',
- 'optionvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'eventpart' ], [ 'optionname' ] ],
- },
-
- 'part_event_condition' => {
- 'columns' => [
- 'eventconditionnum', 'serial', '', '', '', '',
- 'eventpart', 'int', '', '', '', '',
- 'conditionname', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'eventconditionnum',
- 'unique' => [],
- 'index' => [ [ 'eventpart' ], [ 'conditionname' ] ],
- },
-
- 'part_event_condition_option' => {
- 'columns' => [
- 'optionnum', 'serial', '', '', '', '',
- 'eventconditionnum', 'int', '', '', '', '',
- 'optionname', 'varchar', '', $char_d, '', '',
- 'optionvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'eventconditionnum' ], [ 'optionname' ] ],
- },
-
- 'part_event_condition_option_option' => {
- 'columns' => [
- 'optionoptionnum', 'serial', '', '', '', '',
- 'optionnum', 'int', '', '', '', '',
- 'optionname', 'varchar', '', $char_d, '', '',
- 'optionvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'optionoptionnum',
- 'unique' => [],
- 'index' => [ [ 'optionnum' ], [ 'optionname' ] ],
- },
-
- 'cust_event' => {
- 'columns' => [
- 'eventnum', 'serial', '', '', '', '',
- 'eventpart', 'int', '', '', '', '',
- 'tablenum', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'status', 'varchar', '', $char_d, '', '',
- 'statustext', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'eventnum',
- #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
- 'unique' => [],
- 'index' => [ ['eventpart'], ['tablenum'], ['status'] ],
- },
-
- 'cust_bill_pkg' => {
- 'columns' => [
- 'billpkgnum', 'serial', '', '', '', '',
- 'pkgnum', 'int', '', '', '', '',
- 'invnum', 'int', '', '', '', '',
- 'setup', @money_type, '', '',
- 'recur', @money_type, '', '',
- 'sdate', @date_type, '', '',
- 'edate', @date_type, '', '',
- 'itemdesc', 'varchar', 'NULL', $char_d, '', '',
- ],
- 'primary_key' => 'billpkgnum',
- 'unique' => [],
- 'index' => [ ['invnum'], [ 'pkgnum' ] ],
- },
-
- 'cust_bill_pkg_detail' => {
- 'columns' => [
- 'detailnum', 'serial', '', '', '', '',
- 'pkgnum', 'int', '', '', '', '',
- 'invnum', 'int', '', '', '', '',
- 'detail', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'detailnum',
- 'unique' => [],
- 'index' => [ [ 'pkgnum', 'invnum' ] ],
- },
-
- 'cust_credit' => {
- 'columns' => [
- 'crednum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'amount', @money_type, '', '',
- 'otaker', 'varchar', '', 32, '', '',
- 'reason', 'text', 'NULL', '', '', '',
- 'reasonnum', 'int', 'NULL', '', '', '',
- 'closed', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'crednum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['_date'] ],
- },
-
- 'cust_credit_bill' => {
- 'columns' => [
- 'creditbillnum', 'serial', '', '', '', '',
- 'crednum', 'int', '', '', '', '',
- 'invnum', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'amount', @money_type, '', '',
- ],
- 'primary_key' => 'creditbillnum',
- 'unique' => [],
- 'index' => [ ['crednum'], ['invnum'] ],
- },
-
- 'cust_credit_bill_pkg' => {
- 'columns' => [
- 'creditbillpkgnum', 'serial', '', '', '', '',
- 'creditbillnum', 'int', '', '', '', '',
- 'billpkgnum', 'int', '', '', '', '',
- 'amount', @money_type, '', '',
- 'setuprecur', 'varchar', '', $char_d, '', '',
- 'sdate', @date_type, '', '',
- 'edate', @date_type, '', '',
- ],
- 'primary_key' => 'creditbillpkgnum',
- 'unique' => [],
- 'index' => [ [ 'creditbillnum' ], [ 'billpkgnum' ], ],
- },
-
- 'cust_main' => {
- 'columns' => [
- 'custnum', 'serial', '', '', '', '',
- 'agentnum', 'int', '', '', '', '',
- 'agent_custid', 'varchar', 'NULL', $char_d, '', '',
-# 'titlenum', 'int', 'NULL', '', '', '',
- 'last', 'varchar', '', $char_d, '', '',
-# 'middle', 'varchar', 'NULL', $char_d, '', '',
- 'first', 'varchar', '', $char_d, '', '',
- 'ss', 'varchar', 'NULL', 11, '', '',
- 'stateid', 'varchar', 'NULL', $char_d, '', '',
- 'stateid_state', 'varchar', 'NULL', $char_d, '', '',
- 'birthdate' ,@date_type, '', '',
- 'signupdate',@date_type, '', '',
- 'company', 'varchar', 'NULL', $char_d, '', '',
- 'address1', 'varchar', '', $char_d, '', '',
- 'address2', 'varchar', 'NULL', $char_d, '', '',
- 'city', 'varchar', '', $char_d, '', '',
- 'county', 'varchar', 'NULL', $char_d, '', '',
- 'state', 'varchar', 'NULL', $char_d, '', '',
- 'zip', 'varchar', 'NULL', 10, '', '',
- 'country', 'char', '', 2, '', '',
- 'daytime', 'varchar', 'NULL', 20, '', '',
- 'night', 'varchar', 'NULL', 20, '', '',
- 'fax', 'varchar', 'NULL', 12, '', '',
- 'ship_last', 'varchar', 'NULL', $char_d, '', '',
-# 'ship_middle', 'varchar', 'NULL', $char_d, '', '',
- 'ship_first', 'varchar', 'NULL', $char_d, '', '',
- 'ship_company', 'varchar', 'NULL', $char_d, '', '',
- 'ship_address1', 'varchar', 'NULL', $char_d, '', '',
- 'ship_address2', 'varchar', 'NULL', $char_d, '', '',
- 'ship_city', 'varchar', 'NULL', $char_d, '', '',
- 'ship_county', 'varchar', 'NULL', $char_d, '', '',
- 'ship_state', 'varchar', 'NULL', $char_d, '', '',
- 'ship_zip', 'varchar', 'NULL', 10, '', '',
- 'ship_country', 'char', 'NULL', 2, '', '',
- 'ship_daytime', 'varchar', 'NULL', 20, '', '',
- 'ship_night', 'varchar', 'NULL', 20, '', '',
- 'ship_fax', 'varchar', 'NULL', 12, '', '',
- 'payby', 'char', '', 4, '', '',
- 'payinfo', 'varchar', 'NULL', 512, '', '',
- 'paycvv', 'varchar', 'NULL', 512, '', '',
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
- #'paydate', @date_type, '', '',
- 'paydate', 'varchar', 'NULL', 10, '', '',
- 'paystart_month', 'int', 'NULL', '', '', '',
- 'paystart_year', 'int', 'NULL', '', '', '',
- 'payissue', 'varchar', 'NULL', 2, '', '',
- 'payname', 'varchar', 'NULL', $char_d, '', '',
- 'paystate', 'varchar', 'NULL', $char_d, '', '',
- 'paytype', 'varchar', 'NULL', $char_d, '', '',
- 'payip', 'varchar', 'NULL', 15, '', '',
- 'tax', 'char', 'NULL', 1, '', '',
- 'otaker', 'varchar', '', 32, '', '',
- 'refnum', 'int', '', '', '', '',
- 'referral_custnum', 'int', 'NULL', '', '', '',
- 'comments', 'text', 'NULL', '', '', '',
- 'spool_cdr','char', 'NULL', 1, '', '',
- 'invoice_terms', 'varchar', 'NULL', $char_d, '', '',
- ],
- 'primary_key' => 'custnum',
- 'unique' => [ [ 'agentnum', 'agent_custid' ] ],
- #'index' => [ ['last'], ['company'] ],
- 'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ],
- [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ],
- [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ],
- [ 'ship_last' ], [ 'ship_company' ],
- [ 'ship_daytime' ], [ 'ship_night' ], [ 'ship_fax' ],
- [ 'payby' ], [ 'paydate' ],
- ],
- },
-
- 'cust_main_invoice' => {
- 'columns' => [
- 'destnum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- 'dest', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'destnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ],
- },
-
- 'cust_main_note' => {
- 'columns' => [
- 'notenum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'otaker', 'varchar', '', 32, '', '',
- 'comments', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'notenum',
- 'unique' => [],
- 'index' => [ [ 'custnum' ], [ '_date' ], ],
- },
-
- 'cust_main_county' => { #county+state+country are checked off the
- #cust_main_county for validation and to provide
- # a tax rate.
- 'columns' => [
- 'taxnum', 'serial', '', '', '', '',
- 'state', 'varchar', 'NULL', $char_d, '', '',
- 'county', 'varchar', 'NULL', $char_d, '', '',
- 'country', 'char', '', 2, '', '',
- 'taxclass', 'varchar', 'NULL', $char_d, '', '',
- 'exempt_amount', @money_type, '', '',
- 'tax', 'real', '', '', '', '', #tax %
- 'taxname', 'varchar', 'NULL', $char_d, '', '',
- 'setuptax', 'char', 'NULL', 1, '', '', # Y = setup tax exempt
- 'recurtax', 'char', 'NULL', 1, '', '', # Y = recur tax exempt
- ],
- 'primary_key' => 'taxnum',
- 'unique' => [],
- # 'unique' => [ ['taxnum'], ['state', 'county'] ],
- 'index' => [ [ 'county' ], [ 'state' ], [ 'country' ] ],
- },
-
- 'cust_pay_pending' => {
- 'columns' => [
- 'paypendingnum','serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- 'paid', @money_type, '', '',
- '_date', @date_type, '', '',
- 'payby', 'char', '', 4, '', '', #CARD/BILL/COMP, should
- # be index into payby
- # table eventually
- 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
- 'paydate', 'varchar', 'NULL', 10, '', '',
- #'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
- 'payunique', 'varchar', 'NULL', $char_d, '', '', #separate paybatch "unique" functions from current usage
-
- 'status', 'varchar', '', $char_d, '', '',
- 'statustext', 'text', 'NULL', '', '', '',
- 'gatewaynum', 'int', 'NULL', '', '', '',
- #'cust_balance', @money_type, '', '',
- 'paynum', 'int', 'NULL', '', '', '',
- ],
- 'primary_key' => 'paypendingnum',
- 'unique' => [ [ 'payunique' ] ],
- 'index' => [ [ 'custnum' ], [ 'status' ], ],
- },
-
- 'cust_pay' => {
- 'columns' => [
- 'paynum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'paid', @money_type, '', '',
- 'otaker', 'varchar', 'NULL', 32, '', '', #NULL for the upgrade so we can create & populate the field
- 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be
- # index into payby table
- # eventually
- 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
- 'paydate', 'varchar', 'NULL', 10, '', '',
- 'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
- 'payunique', 'varchar', 'NULL', $char_d, '', '', #separate paybatch "unique" functions from current usage
- 'closed', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'paynum',
- #i guess not now, with cust_pay_pending, if we actually make it here, we _do_ want to record it# 'unique' => [ [ 'payunique' ] ],
- 'index' => [ [ 'custnum' ], [ 'paybatch' ], [ 'payby' ], [ '_date' ] ],
- },
-
- 'cust_pay_void' => {
- 'columns' => [
- 'paynum', 'int', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- 'paid', @money_type, '', '',
- '_date', @date_type, '', '',
- 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be
- # index into payby table
- # eventually
- 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
- 'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
- 'closed', 'char', 'NULL', 1, '', '',
- 'void_date', @date_type, '', '',
- 'reason', 'varchar', 'NULL', $char_d, '', '',
- 'otaker', 'varchar', '', 32, '', '',
- ],
- 'primary_key' => 'paynum',
- 'unique' => [],
- 'index' => [ [ 'custnum' ] ],
- },
-
- 'cust_bill_pay' => {
- 'columns' => [
- 'billpaynum', 'serial', '', '', '', '',
- 'invnum', 'int', '', '', '', '',
- 'paynum', 'int', '', '', '', '',
- 'amount', @money_type, '', '',
- '_date', @date_type, '', '',
- ],
- 'primary_key' => 'billpaynum',
- 'unique' => [],
- 'index' => [ [ 'paynum' ], [ 'invnum' ] ],
- },
-
- 'cust_bill_pay_batch' => {
- 'columns' => [
- 'billpaynum', 'serial', '', '', '', '',
- 'invnum', 'int', '', '', '', '',
- 'paybatchnum', 'int', '', '', '', '',
- 'amount', @money_type, '', '',
- '_date', @date_type, '', '',
- ],
- 'primary_key' => 'billpaynum',
- 'unique' => [],
- 'index' => [ [ 'paybatchnum' ], [ 'invnum' ] ],
- },
-
- 'cust_bill_pay_pkg' => {
- 'columns' => [
- 'billpaypkgnum', 'serial', '', '', '', '',
- 'billpaynum', 'int', '', '', '', '',
- 'billpkgnum', 'int', '', '', '', '',
- 'amount', @money_type, '', '',
- 'setuprecur', 'varchar', '', $char_d, '', '',
- 'sdate', @date_type, '', '',
- 'edate', @date_type, '', '',
- ],
- 'primary_key' => 'billpaypkgnum',
- 'unique' => [],
- 'index' => [ [ 'billpaynum' ], [ 'billpkgnum' ], ],
- },
-
- 'pay_batch' => { #batches of payments to an external processor
- 'columns' => [
- 'batchnum', 'serial', '', '', '', '',
- 'payby', 'char', '', 4, '', '', # CARD/CHEK
- 'status', 'char', 'NULL', 1, '', '',
- 'download', @date_type, '', '',
- 'upload', @date_type, '', '',
- ],
- 'primary_key' => 'batchnum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'cust_pay_batch' => { #what's this used for again? list of customers
- #in current CARD batch? (necessarily CARD?)
- 'columns' => [
- 'paybatchnum', 'serial', '', '', '', '',
- 'batchnum', 'int', '', '', '', '',
- 'invnum', 'int', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- 'last', 'varchar', '', $char_d, '', '',
- 'first', 'varchar', '', $char_d, '', '',
- 'address1', 'varchar', '', $char_d, '', '',
- 'address2', 'varchar', 'NULL', $char_d, '', '',
- 'city', 'varchar', '', $char_d, '', '',
- 'state', 'varchar', 'NULL', $char_d, '', '',
- 'zip', 'varchar', 'NULL', 10, '', '',
- 'country', 'char', '', 2, '', '',
- # 'trancode', 'int', '', '', '', ''
- 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be
- 'payinfo', 'varchar', '', 512, '', '',
- #'exp', @date_type, '', ''
- 'exp', 'varchar', 'NULL', 11, '', '',
- 'payname', 'varchar', 'NULL', $char_d, '', '',
- 'amount', @money_type, '', '',
- 'status', 'varchar', 'NULL', $char_d, '', '',
- ],
- 'primary_key' => 'paybatchnum',
- 'unique' => [],
- 'index' => [ ['batchnum'], ['invnum'], ['custnum'] ],
- },
-
- 'cust_pkg' => {
- 'columns' => [
- 'pkgnum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- 'pkgpart', 'int', '', '', '', '',
- 'otaker', 'varchar', '', 32, '', '',
- 'setup', @date_type, '', '',
- 'bill', @date_type, '', '',
- 'last_bill', @date_type, '', '',
- 'susp', @date_type, '', '',
- 'adjourn', @date_type, '', '',
- 'cancel', @date_type, '', '',
- 'expire', @date_type, '', '',
- 'change_date', @date_type, '', '',
- 'change_pkgnum', 'int', 'NULL', '', '', '',
- 'change_pkgpart', 'int', 'NULL', '', '', '',
- 'manual_flag', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'pkgnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['pkgpart'],
- ['setup'], ['last_bill'], ['bill'], ['susp'], ['adjourn'],
- ['expire'], ['cancel'],
- ['change_date'],
- ],
- },
-
- 'cust_pkg_option' => {
- 'columns' => [
- 'optionnum', 'serial', '', '', '', '',
- 'pkgnum', 'int', '', '', '', '',
- 'optionname', 'varchar', '', $char_d, '', '',
- 'optionvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'pkgnum' ], [ 'optionname' ] ],
- },
-
- 'cust_pkg_reason' => {
- 'columns' => [
- 'num', 'serial', '', '', '', '',
- 'pkgnum', 'int', '', '', '', '',
- 'reasonnum','int', '', '', '', '',
- 'otaker', 'varchar', '', 32, '', '',
- 'date', @date_type, '', '',
- ],
- 'primary_key' => 'num',
- 'unique' => [],
- 'index' => [],
- },
-
- 'cust_refund' => {
- 'columns' => [
- 'refundnum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'refund', @money_type, '', '',
- 'otaker', 'varchar', '', 32, '', '',
- 'reason', 'varchar', '', $char_d, '', '',
- 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should
- # be index into payby
- # table eventually
- 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
- 'paybatch', 'varchar', 'NULL', $char_d, '', '',
- 'closed', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'refundnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['_date'] ],
- },
-
- 'cust_credit_refund' => {
- 'columns' => [
- 'creditrefundnum', 'serial', '', '', '', '',
- 'crednum', 'int', '', '', '', '',
- 'refundnum', 'int', '', '', '', '',
- 'amount', @money_type, '', '',
- '_date', @date_type, '', '',
- ],
- 'primary_key' => 'creditrefundnum',
- 'unique' => [],
- 'index' => [ ['crednum'], ['refundnum'] ],
- },
-
-
- 'cust_svc' => {
- 'columns' => [
- 'svcnum', 'serial', '', '', '', '',
- 'pkgnum', 'int', 'NULL', '', '', '',
- 'svcpart', 'int', '', '', '', '',
- 'overlimit', @date_type, '', '',
- ],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [ ['svcnum'], ['pkgnum'], ['svcpart'] ],
- },
-
- 'part_pkg' => {
- 'columns' => [
- 'pkgpart', 'serial', '', '', '', '',
- 'pkg', 'varchar', '', $char_d, '', '',
- 'comment', 'varchar', '', $char_d, '', '',
- 'promo_code', 'varchar', 'NULL', $char_d, '', '',
- 'setup', @perl_type, '', '',
- 'freq', 'varchar', '', $char_d, '', '', #billing frequency
- 'recur', @perl_type, '', '',
- 'setuptax', 'char', 'NULL', 1, '', '',
- 'recurtax', 'char', 'NULL', 1, '', '',
- 'plan', 'varchar', 'NULL', $char_d, '', '',
- 'plandata', 'text', 'NULL', '', '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- 'taxclass', 'varchar', 'NULL', $char_d, '', '',
- 'classnum', 'int', 'NULL', '', '', '',
- 'pay_weight', 'real', 'NULL', '', '', '',
- 'credit_weight', 'real', 'NULL', '', '', '',
- 'agentnum', 'int', 'NULL', '', '', '',
-
- ],
- 'primary_key' => 'pkgpart',
- 'unique' => [],
- 'index' => [ [ 'promo_code' ], [ 'disabled' ], [ 'agentnum' ], ],
- },
-
- 'part_pkg_taxclass' => {
- 'columns' => [
- 'taxclassnum', 'serial', '', '', '', '',
- 'taxclass', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'taxclassnum',
- 'unique' => [ [ 'taxclass' ] ],
- 'index' => [],
- },
-
-# 'part_title' => {
-# 'columns' => [
-# 'titlenum', 'int', '', '',
-# 'title', 'varchar', '', $char_d,
-# ],
-# 'primary_key' => 'titlenum',
-# 'unique' => [ [] ],
-# 'index' => [ [] ],
-# },
-
- 'pkg_svc' => {
- 'columns' => [
- 'pkgsvcnum', 'serial', '', '', '', '',
- 'pkgpart', 'int', '', '', '', '',
- 'svcpart', 'int', '', '', '', '',
- 'quantity', 'int', '', '', '', '',
- 'primary_svc','char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'pkgsvcnum',
- 'unique' => [ ['pkgpart', 'svcpart'] ],
- 'index' => [ ['pkgpart'] ],
- },
-
- 'part_referral' => {
- 'columns' => [
- 'refnum', 'serial', '', '', '', '',
- 'referral', 'varchar', '', $char_d, '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- 'agentnum', 'int', 'NULL', '', '', '',
- ],
- 'primary_key' => 'refnum',
- 'unique' => [],
- 'index' => [ ['disabled'], ['agentnum'], ],
- },
-
- 'part_svc' => {
- 'columns' => [
- 'svcpart', 'serial', '', '', '', '',
- 'svc', 'varchar', '', $char_d, '', '',
- 'svcdb', 'varchar', '', $char_d, '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'svcpart',
- 'unique' => [],
- 'index' => [ [ 'disabled' ] ],
- },
-
- 'part_svc_column' => {
- 'columns' => [
- 'columnnum', 'serial', '', '', '', '',
- 'svcpart', 'int', '', '', '', '',
- 'columnname', 'varchar', '', 64, '', '',
- 'columnvalue', 'varchar', 'NULL', $char_d, '', '',
- 'columnflag', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'columnnum',
- 'unique' => [ [ 'svcpart', 'columnname' ] ],
- 'index' => [ [ 'svcpart' ] ],
- },
-
- #(this should be renamed to part_pop)
- 'svc_acct_pop' => {
- 'columns' => [
- 'popnum', 'serial', '', '', '', '',
- 'city', 'varchar', '', $char_d, '', '',
- 'state', 'varchar', '', $char_d, '', '',
- 'ac', 'char', '', 3, '', '',
- 'exch', 'char', '', 3, '', '',
- 'loc', 'char', 'NULL', 4, '', '', #NULL for legacy purposes
- ],
- 'primary_key' => 'popnum',
- 'unique' => [],
- 'index' => [ [ 'state' ] ],
- },
-
- 'part_pop_local' => {
- 'columns' => [
- 'localnum', 'serial', '', '', '', '',
- 'popnum', 'int', '', '', '', '',
- 'city', 'varchar', 'NULL', $char_d, '', '',
- 'state', 'char', 'NULL', 2, '', '',
- 'npa', 'char', '', 3, '', '',
- 'nxx', 'char', '', 3, '', '',
- ],
- 'primary_key' => 'localnum',
- 'unique' => [],
- 'index' => [ [ 'npa', 'nxx' ], [ 'popnum' ] ],
- },
-
- 'svc_acct' => {
- 'columns' => [
- 'svcnum', 'int', '', '', '', '',
- 'username', 'varchar', '', $username_len, '', '',
- '_password', 'varchar', '', 512, '', '',
- '_password_encoding', 'varchar', 'NULL', $char_d, '', '',
- 'sec_phrase', 'varchar', 'NULL', $char_d, '', '',
- 'popnum', 'int', 'NULL', '', '', '',
- 'uid', 'int', 'NULL', '', '', '',
- 'gid', 'int', 'NULL', '', '', '',
- 'finger', 'varchar', 'NULL', $char_d, '', '',
- 'dir', 'varchar', 'NULL', $char_d, '', '',
- 'shell', 'varchar', 'NULL', $char_d, '', '',
- 'quota', 'varchar', 'NULL', $char_d, '', '',
- 'slipip', 'varchar', 'NULL', 15, '', '', #four TINYINTs, bah.
- 'seconds', 'int', 'NULL', '', '', '', #uhhhh
- 'seconds_threshold', 'int', 'NULL', '', '', '',
- 'upbytes', 'bigint', 'NULL', '', '', '',
- 'upbytes_threshold', 'bigint', 'NULL', '', '', '',
- 'downbytes', 'bigint', 'NULL', '', '', '',
- 'downbytes_threshold', 'bigint', 'NULL', '', '', '',
- 'totalbytes','bigint', 'NULL', '', '', '',
- 'totalbytes_threshold', 'bigint', 'NULL', '', '', '',
- 'domsvc', 'int', '', '', '', '',
- 'last_login', @date_type, '', '',
- 'last_logout', @date_type, '', '',
- ],
- 'primary_key' => 'svcnum',
- #'unique' => [ [ 'username', 'domsvc' ] ],
- 'unique' => [],
- 'index' => [ ['username'], ['domsvc'] ],
- },
-
- 'acct_rt_transaction' => {
- 'columns' => [
- 'svcrtid', 'int', '', '', '', '',
- 'svcnum', 'int', '', '', '', '',
- 'transaction_id', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'seconds', 'int', '', '', '', '', #uhhhh
- 'support', 'int', '', '', '', '',
- ],
- 'primary_key' => 'svcrtid',
- 'unique' => [],
- 'index' => [ ['svcnum', 'transaction_id'] ],
- },
-
- #'svc_charge' => {
- # 'columns' => [
- # 'svcnum', 'int', '', '',
- # 'amount', @money_type,
- # ],
- # 'primary_key' => 'svcnum',
- # 'unique' => [ [] ],
- # 'index' => [ [] ],
- #},
-
- 'svc_domain' => {
- 'columns' => [
- 'svcnum', 'int', '', '', '', '',
- 'domain', 'varchar', '', $char_d, '', '',
- 'suffix', 'varchar', 'NULL', $char_d, '', '',
- 'catchall', 'int', 'NULL', '', '', '',
- 'parent_svcnum', 'int', 'NULL', '', '', '',
- 'registrarnum', 'int', 'NULL', '', '', '',
- 'registrarkey', 'varchar', 'NULL', 512, '', '',
- 'setup_date', @date_type, '', '',
- 'renewal_interval', 'int', 'NULL', '', '', '',
- 'expiration_date', @date_type, '', '',
- ],
- 'primary_key' => 'svcnum',
- 'unique' => [ ],
- 'index' => [ ['domain'] ],
- },
-
- 'domain_record' => {
- 'columns' => [
- 'recnum', 'serial', '', '', '', '',
- 'svcnum', 'int', '', '', '', '',
- 'reczone', 'varchar', '', 255, '', '',
- 'recaf', 'char', '', 2, '', '',
- 'rectype', 'varchar', '', 5, '', '',
- 'recdata', 'varchar', '', 255, '', '',
- ],
- 'primary_key' => 'recnum',
- 'unique' => [],
- 'index' => [ ['svcnum'] ],
- },
-
- 'registrar' => {
- 'columns' => [
- 'registrarnum', 'serial', '', '', '', '',
- 'registrarname', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'registrarnum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'svc_forward' => {
- 'columns' => [
- 'svcnum', 'int', '', '', '', '',
- 'srcsvc', 'int', 'NULL', '', '', '',
- 'src', 'varchar', 'NULL', 255, '', '',
- 'dstsvc', 'int', 'NULL', '', '', '',
- 'dst', 'varchar', 'NULL', 255, '', '',
- ],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [ ['srcsvc'], ['dstsvc'] ],
- },
-
- 'svc_www' => {
- 'columns' => [
- 'svcnum', 'int', '', '', '', '',
- 'recnum', 'int', '', '', '', '',
- 'usersvc', 'int', 'NULL', '', '', '',
- 'config', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [],
- },
-
- #'svc_wo' => {
- # 'columns' => [
- # 'svcnum', 'int', '', '',
- # 'svcnum', 'int', '', '',
- # 'svcnum', 'int', '', '',
- # 'worker', 'varchar', '', $char_d,
- # '_date', @date_type,
- # ],
- # 'primary_key' => 'svcnum',
- # 'unique' => [ [] ],
- # 'index' => [ [] ],
- #},
-
- 'prepay_credit' => {
- 'columns' => [
- 'prepaynum', 'serial', '', '', '', '',
- 'identifier', 'varchar', '', $char_d, '', '',
- 'amount', @money_type, '', '',
- 'seconds', 'int', 'NULL', '', '', '',
- 'upbytes', 'bigint', 'NULL', '', '', '',
- 'downbytes', 'bigint', 'NULL', '', '', '',
- 'totalbytes', 'bigint', 'NULL', '', '', '',
- 'agentnum', 'int', 'NULL', '', '', '',
- ],
- 'primary_key' => 'prepaynum',
- 'unique' => [ ['identifier'] ],
- 'index' => [],
- },
-
- 'port' => {
- 'columns' => [
- 'portnum', 'serial', '', '', '', '',
- 'ip', 'varchar', 'NULL', 15, '', '',
- 'nasport', 'int', 'NULL', '', '', '',
- 'nasnum', 'int', '', '', '', '',
- ],
- 'primary_key' => 'portnum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'nas' => {
- 'columns' => [
- 'nasnum', 'serial', '', '', '', '',
- 'nas', 'varchar', '', $char_d, '', '',
- 'nasip', 'varchar', '', 15, '', '',
- 'nasfqdn', 'varchar', '', $char_d, '', '',
- 'last', 'int', '', '', '', '',
- ],
- 'primary_key' => 'nasnum',
- 'unique' => [ [ 'nas' ], [ 'nasip' ] ],
- 'index' => [ [ 'last' ] ],
- },
-
-# 'session' => {
-# 'columns' => [
-# 'sessionnum', 'serial', '', '', '', '',
-# 'portnum', 'int', '', '', '', '',
-# 'svcnum', 'int', '', '', '', '',
-# 'login', @date_type, '', '',
-# 'logout', @date_type, '', '',
-# ],
-# 'primary_key' => 'sessionnum',
-# 'unique' => [],
-# 'index' => [ [ 'portnum' ] ],
-# },
-
- 'queue' => {
- 'columns' => [
- 'jobnum', 'serial', '', '', '', '',
- 'job', 'text', '', '', '', '',
- '_date', 'int', '', '', '', '',
- 'status', 'varchar', '', $char_d, '', '',
- 'statustext', 'text', 'NULL', '', '', '',
- 'svcnum', 'int', 'NULL', '', '', '',
- 'secure', 'char', 'NULL', 1, '', '', # Y = needs to be run on machine
- # w/private key
- ],
- 'primary_key' => 'jobnum',
- 'unique' => [],
- 'index' => [ [ 'svcnum' ], [ 'status' ] ],
- },
-
- 'queue_arg' => {
- 'columns' => [
- 'argnum', 'serial', '', '', '', '',
- 'jobnum', 'int', '', '', '', '',
- 'arg', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'argnum',
- 'unique' => [],
- 'index' => [ [ 'jobnum' ] ],
- },
-
- 'queue_depend' => {
- 'columns' => [
- 'dependnum', 'serial', '', '', '', '',
- 'jobnum', 'int', '', '', '', '',
- 'depend_jobnum', 'int', '', '', '', '',
- ],
- 'primary_key' => 'dependnum',
- 'unique' => [],
- 'index' => [ [ 'jobnum' ], [ 'depend_jobnum' ] ],
- },
-
- 'export_svc' => {
- 'columns' => [
- 'exportsvcnum' => 'serial', '', '', '', '',
- 'exportnum' => 'int', '', '', '', '',
- 'svcpart' => 'int', '', '', '', '',
- ],
- 'primary_key' => 'exportsvcnum',
- 'unique' => [ [ 'exportnum', 'svcpart' ] ],
- 'index' => [ [ 'exportnum' ], [ 'svcpart' ] ],
- },
-
- 'part_export' => {
- 'columns' => [
- 'exportnum', 'serial', '', '', '', '',
- 'machine', 'varchar', '', $char_d, '', '',
- 'exporttype', 'varchar', '', $char_d, '', '',
- 'nodomain', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'exportnum',
- 'unique' => [],
- 'index' => [ [ 'machine' ], [ 'exporttype' ] ],
- },
-
- 'part_export_option' => {
- 'columns' => [
- 'optionnum', 'serial', '', '', '', '',
- 'exportnum', 'int', '', '', '', '',
- 'optionname', 'varchar', '', $char_d, '', '',
- 'optionvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'exportnum' ], [ 'optionname' ] ],
- },
-
- 'radius_usergroup' => {
- 'columns' => [
- 'usergroupnum', 'serial', '', '', '', '',
- 'svcnum', 'int', '', '', '', '',
- 'groupname', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'usergroupnum',
- 'unique' => [],
- 'index' => [ [ 'svcnum' ], [ 'groupname' ] ],
- },
-
- 'msgcat' => {
- 'columns' => [
- 'msgnum', 'serial', '', '', '', '',
- 'msgcode', 'varchar', '', $char_d, '', '',
- 'locale', 'varchar', '', 16, '', '',
- 'msg', 'text', '', '', '', '',
- ],
- 'primary_key' => 'msgnum',
- 'unique' => [ [ 'msgcode', 'locale' ] ],
- 'index' => [],
- },
-
- 'cust_tax_exempt' => {
- 'columns' => [
- 'exemptnum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
- 'taxnum', 'int', '', '', '', '',
- 'year', 'int', '', '', '', '',
- 'month', 'int', '', '', '', '',
- 'amount', @money_type, '', '',
- ],
- 'primary_key' => 'exemptnum',
- 'unique' => [ [ 'custnum', 'taxnum', 'year', 'month' ] ],
- 'index' => [],
- },
-
- 'cust_tax_exempt_pkg' => {
- 'columns' => [
- 'exemptpkgnum', 'serial', '', '', '', '',
- #'custnum', 'int', '', '', '', ''
- 'billpkgnum', 'int', '', '', '', '',
- 'taxnum', 'int', '', '', '', '',
- 'year', 'int', '', '', '', '',
- 'month', 'int', '', '', '', '',
- 'amount', @money_type, '', '',
- ],
- 'primary_key' => 'exemptpkgnum',
- 'unique' => [],
- 'index' => [ [ 'taxnum', 'year', 'month' ],
- [ 'billpkgnum' ],
- [ 'taxnum' ]
- ],
- },
-
- 'router' => {
- 'columns' => [
- 'routernum', 'serial', '', '', '', '',
- 'routername', 'varchar', '', $char_d, '', '',
- 'svcnum', 'int', 'NULL', '', '', '',
- ],
- 'primary_key' => 'routernum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'part_svc_router' => {
- 'columns' => [
- 'svcrouternum', 'serial', '', '', '', '',
- 'svcpart', 'int', '', '', '', '',
- 'routernum', 'int', '', '', '', '',
- ],
- 'primary_key' => 'svcrouternum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'addr_block' => {
- 'columns' => [
- 'blocknum', 'serial', '', '', '', '',
- 'routernum', 'int', '', '', '', '',
- 'ip_gateway', 'varchar', '', 15, '', '',
- 'ip_netmask', 'int', '', '', '', '',
- ],
- 'primary_key' => 'blocknum',
- 'unique' => [ [ 'blocknum', 'routernum' ] ],
- 'index' => [],
- },
-
- 'svc_broadband' => {
- 'columns' => [
- 'svcnum', 'int', '', '', '', '',
- 'description', 'varchar', 'NULL', $char_d, '', '',
- 'blocknum', 'int', '', '', '', '',
- 'speed_up', 'int', '', '', '', '',
- 'speed_down', 'int', '', '', '', '',
- 'ip_addr', 'varchar', '', 15, '', '',
- 'mac_addr', 'varchar', 'NULL', 12, '', '',
- 'authkey', 'varchar', 'NULL', 32, '', '',
- 'latitude', 'decimal', 'NULL', '', '', '',
- 'longitude', 'decimal', 'NULL', '', '', '',
- 'altitude', 'decimal', 'NULL', '', '', '',
- 'vlan_profile', 'varchar', 'NULL', $char_d, '', '',
- ],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'part_virtual_field' => {
- 'columns' => [
- 'vfieldpart', 'serial', '', '', '', '',
- 'dbtable', 'varchar', '', 32, '', '',
- 'name', 'varchar', '', 32, '', '',
- 'check_block', 'text', 'NULL', '', '', '',
- 'length', 'int', 'NULL', '', '', '',
- 'list_source', 'text', 'NULL', '', '', '',
- 'label', 'varchar', 'NULL', 80, '', '',
- ],
- 'primary_key' => 'vfieldpart',
- 'unique' => [],
- 'index' => [],
- },
-
- 'virtual_field' => {
- 'columns' => [
- 'vfieldnum', 'serial', '', '', '', '',
- 'recnum', 'int', '', '', '', '',
- 'vfieldpart', 'int', '', '', '', '',
- 'value', 'varchar', '', 128, '', '',
- ],
- 'primary_key' => 'vfieldnum',
- 'unique' => [ [ 'vfieldpart', 'recnum' ] ],
- 'index' => [],
- },
-
- 'acct_snarf' => {
- 'columns' => [
- 'snarfnum', 'int', '', '', '', '',
- 'svcnum', 'int', '', '', '', '',
- 'machine', 'varchar', '', 255, '', '',
- 'protocol', 'varchar', '', $char_d, '', '',
- 'username', 'varchar', '', $char_d, '', '',
- '_password', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'snarfnum',
- 'unique' => [],
- 'index' => [ [ 'svcnum' ] ],
- },
-
- 'svc_external' => {
- 'columns' => [
- 'svcnum', 'int', '', '', '', '',
- 'id', 'int', 'NULL', '', '', '',
- 'title', 'varchar', 'NULL', $char_d, '', '',
- ],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'cust_pay_refund' => {
- 'columns' => [
- 'payrefundnum', 'serial', '', '', '', '',
- 'paynum', 'int', '', '', '', '',
- 'refundnum', 'int', '', '', '', '',
- '_date', @date_type, '', '',
- 'amount', @money_type, '', '',
- ],
- 'primary_key' => 'payrefundnum',
- 'unique' => [],
- 'index' => [ ['paynum'], ['refundnum'] ],
- },
-
- 'part_pkg_option' => {
- 'columns' => [
- 'optionnum', 'serial', '', '', '', '',
- 'pkgpart', 'int', '', '', '', '',
- 'optionname', 'varchar', '', $char_d, '', '',
- 'optionvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'pkgpart' ], [ 'optionname' ] ],
- },
-
- 'rate' => {
- 'columns' => [
- 'ratenum', 'serial', '', '', '', '',
- 'ratename', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'ratenum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'rate_detail' => {
- 'columns' => [
- 'ratedetailnum', 'serial', '', '', '', '',
- 'ratenum', 'int', '', '', '', '',
- 'orig_regionnum', 'int', 'NULL', '', '', '',
- 'dest_regionnum', 'int', '', '', '', '',
- 'min_included', 'int', '', '', '', '',
- #'min_charge', @money_type, '', '',
- 'min_charge', 'decimal', '', '10,5', '', '',
- 'sec_granularity', 'int', '', '', '', '',
- #time period (link to table of periods)?
- ],
- 'primary_key' => 'ratedetailnum',
- 'unique' => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ],
- 'index' => [ [ 'ratenum', 'dest_regionnum' ] ],
- },
-
- 'rate_region' => {
- 'columns' => [
- 'regionnum', 'serial', '', '', '', '',
- 'regionname', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'regionnum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'rate_prefix' => {
- 'columns' => [
- 'prefixnum', 'serial', '', '', '', '',
- 'regionnum', 'int', '', '',, '', '',
- 'countrycode', 'varchar', '', 3, '', '',
- 'npa', 'varchar', 'NULL', 6, '', '',
- 'nxx', 'varchar', 'NULL', 3, '', '',
- ],
- 'primary_key' => 'prefixnum',
- 'unique' => [],
- 'index' => [ [ 'countrycode' ], [ 'regionnum' ] ],
- },
-
- 'reg_code' => {
- 'columns' => [
- 'codenum', 'serial', '', '', '', '',
- 'code', 'varchar', '', $char_d, '', '',
- 'agentnum', 'int', '', '', '', '',
- ],
- 'primary_key' => 'codenum',
- 'unique' => [ [ 'agentnum', 'code' ] ],
- 'index' => [ [ 'agentnum' ] ],
- },
-
- 'reg_code_pkg' => {
- 'columns' => [
- 'codepkgnum', 'serial', '', '', '', '',
- 'codenum', 'int', '', '', '', '',
- 'pkgpart', 'int', '', '', '', '',
- ],
- 'primary_key' => 'codepkgnum',
- 'unique' => [ [ 'codenum', 'pkgpart' ] ],
- 'index' => [ [ 'codenum' ] ],
- },
-
- 'clientapi_session' => {
- 'columns' => [
- 'sessionnum', 'serial', '', '', '', '',
- 'sessionid', 'varchar', '', $char_d, '', '',
- 'namespace', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'sessionnum',
- 'unique' => [ [ 'sessionid', 'namespace' ] ],
- 'index' => [],
- },
-
- 'clientapi_session_field' => {
- 'columns' => [
- 'fieldnum', 'serial', '', '', '', '',
- 'sessionnum', 'int', '', '', '', '',
- 'fieldname', 'varchar', '', $char_d, '', '',
- 'fieldvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'fieldnum',
- 'unique' => [ [ 'sessionnum', 'fieldname' ] ],
- 'index' => [],
- },
-
- 'payment_gateway' => {
- 'columns' => [
- 'gatewaynum', 'serial', '', '', '', '',
- 'gateway_module', 'varchar', '', $char_d, '', '',
- 'gateway_username', 'varchar', 'NULL', $char_d, '', '',
- 'gateway_password', 'varchar', 'NULL', $char_d, '', '',
- 'gateway_action', 'varchar', 'NULL', $char_d, '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'gatewaynum',
- 'unique' => [],
- 'index' => [ [ 'disabled' ] ],
- },
-
- 'payment_gateway_option' => {
- 'columns' => [
- 'optionnum', 'serial', '', '', '', '',
- 'gatewaynum', 'int', '', '', '', '',
- 'optionname', 'varchar', '', $char_d, '', '',
- 'optionvalue', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'gatewaynum' ], [ 'optionname' ] ],
- },
-
- 'agent_payment_gateway' => {
- 'columns' => [
- 'agentgatewaynum', 'serial', '', '', '', '',
- 'agentnum', 'int', '', '', '', '',
- 'gatewaynum', 'int', '', '', '', '',
- 'cardtype', 'varchar', 'NULL', $char_d, '', '',
- 'taxclass', 'varchar', 'NULL', $char_d, '', '',
- ],
- 'primary_key' => 'agentgatewaynum',
- 'unique' => [],
- 'index' => [ [ 'agentnum', 'cardtype' ], ],
- },
-
- 'banned_pay' => {
- 'columns' => [
- 'bannum', 'serial', '', '', '', '',
- 'payby', 'char', '', 4, '', '',
- 'payinfo', 'varchar', '', 128, '', '', #say, a 512-big digest _hex encoded
- #'paymask', 'varchar', 'NULL', $char_d, '', ''
- '_date', @date_type, '', '',
- 'otaker', 'varchar', '', 32, '', '',
- 'reason', 'varchar', 'NULL', $char_d, '', '',
- ],
- 'primary_key' => 'bannum',
- 'unique' => [ [ 'payby', 'payinfo' ] ],
- 'index' => [],
- },
-
- 'pkg_class' => {
- 'columns' => [
- 'classnum', 'serial', '', '', '', '',
- 'classname', 'varchar', '', $char_d, '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'classnum',
- 'unique' => [],
- 'index' => [ ['disabled'] ],
- },
-
- 'cdr' => {
- 'columns' => [
- # qw( name type null length default local );
-
- ###
- #asterisk fields
- ###
-
- 'acctid', 'bigserial', '', '', '', '',
- #'calldate', 'TIMESTAMP with time zone', '', '', \'now()', '',
- 'calldate', 'timestamp', '', '', \'now()', '',
- 'clid', 'varchar', '', $char_d, \"''", '',
- 'src', 'varchar', '', $char_d, \"''", '',
- 'dst', 'varchar', '', $char_d, \"''", '',
- 'dcontext', 'varchar', '', $char_d, \"''", '',
- 'channel', 'varchar', '', $char_d, \"''", '',
- 'dstchannel', 'varchar', '', $char_d, \"''", '',
- 'lastapp', 'varchar', '', $char_d, \"''", '',
- 'lastdata', 'varchar', '', $char_d, \"''", '',
-
- #these don't seem to be logged by most of the SQL cdr_* modules
- #except tds under sql-illegal names, so;
- # ... don't rely on them for rating?
- # and, what they hey, i went ahead and changed the names and data types
- # to freeside-style dates...
- #'start', 'timestamp', 'NULL', '', '', '',
- #'answer', 'timestamp', 'NULL', '', '', '',
- #'end', 'timestamp', 'NULL', '', '', '',
- 'startdate', @date_type, '', '',
- 'answerdate', @date_type, '', '',
- 'enddate', @date_type, '', '',
- #
-
- 'duration', 'int', '', '', 0, '',
- 'billsec', 'int', '', '', 0, '',
- 'disposition', 'varchar', '', 45, \"''", '',
- 'amaflags', 'int', '', '', 0, '',
- 'accountcode', 'varchar', '', 20, \"''", '',
- 'uniqueid', 'varchar', '', 32, \"''", '',
- 'userfield', 'varchar', '', 255, \"''", '',
-
- ###
- # fields for unitel/RSLCOM/convergent that don't map well to asterisk
- # defaults
- ###
-
- #cdr_type: Usage = 1, S&E = 7, OC&C = 8
- 'cdrtypenum', 'int', 'NULL', '', '', '',
-
- 'charged_party', 'varchar', 'NULL', $char_d, '', '',
-
- 'upstream_currency', 'char', 'NULL', 3, '', '',
- 'upstream_price', 'decimal', 'NULL', '10,2', '', '',
- 'upstream_rateplanid', 'int', 'NULL', '', '', '', #?
-
- # how it was rated internally...
- 'ratedetailnum', 'int', 'NULL', '', '', '',
- 'rated_price', 'decimal', 'NULL', '10,2', '', '',
-
- 'distance', 'decimal', 'NULL', '', '', '',
- 'islocal', 'int', 'NULL', '', '', '', # '', '', 0, '' instead?
-
- #cdr_calltype: the big list in appendix 2
- 'calltypenum', 'int', 'NULL', '', '', '',
-
- 'description', 'varchar', 'NULL', $char_d, '', '',
- 'quantity', 'int', 'NULL', '', '', '',
-
- #cdr_carrier: Telstra =1, Optus = 2, RSL COM = 3
- 'carrierid', 'int', 'NULL', '', '', '',
-
- 'upstream_rateid', 'int', 'NULL', '', '', '',
-
- ###
- #and now for our own fields
- ###
-
- # a svcnum... right..?
- 'svcnum', 'int', 'NULL', '', '', '',
-
- #NULL, done (or something)
- 'freesidestatus', 'varchar', 'NULL', 32, '', '',
-
- ],
- 'primary_key' => 'acctid',
- 'unique' => [],
- 'index' => [ [ 'calldate' ], [ 'dst' ], [ 'accountcode' ], [ 'freesidestatus' ] ],
- },
-
- 'cdr_calltype' => {
- 'columns' => [
- 'calltypenum', 'serial', '', '', '', '',
- 'calltypename', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'calltypenum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'cdr_type' => {
- 'columns' => [
- 'cdrtypenum' => 'serial', '', '', '', '',
- 'cdrtypename' => 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'cdrtypenum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'cdr_carrier' => {
- 'columns' => [
- 'carrierid' => 'serial', '', '', '', '',
- 'carriername' => 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'carrierid',
- 'unique' => [],
- 'index' => [],
- },
-
- #map upstream rateid to ours...
- 'cdr_upstream_rate' => {
- 'columns' => [
- 'upstreamratenum', 'serial', '', '', '', '',
- 'upstream_rateid', 'varchar', '', $char_d, '', '',
- 'ratedetailnum', 'int', 'NULL', '', '', '',
- ],
- 'primary_key' => 'upstreamratenum', #XXX need a primary key
- 'unique' => [ [ 'upstream_rateid' ] ], #unless we add another field, yeah
- 'index' => [],
- },
-
- 'inventory_item' => {
- 'columns' => [
- 'itemnum', 'serial', '', '', '', '',
- 'classnum', 'int', '', '', '', '',
- 'item', 'varchar', '', $char_d, '', '',
- 'svcnum', 'int', 'NULL', '', '', '',
- ],
- 'primary_key' => 'itemnum',
- 'unique' => [ [ 'classnum', 'item' ] ],
- 'index' => [ [ 'classnum' ], [ 'svcnum' ] ],
- },
-
- 'inventory_class' => {
- 'columns' => [
- 'classnum', 'serial', '', '', '', '',
- 'classname', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'classnum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'access_user' => {
- 'columns' => [
- 'usernum', 'serial', '', '', '', '',
- 'username', 'varchar', '', $char_d, '', '',
- '_password', 'varchar', '', $char_d, '', '',
- 'last', 'varchar', '', $char_d, '', '',
- 'first', 'varchar', '', $char_d, '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'usernum',
- 'unique' => [ [ 'username' ] ],
- 'index' => [],
- },
-
- 'access_user_pref' => {
- 'columns' => [
- 'prefnum', 'serial', '', '', '', '',
- 'usernum', 'int', '', '', '', '',
- 'prefname', 'varchar', '', $char_d, '', '',
- 'prefvalue', 'text', 'NULL', '', '', '',
- 'expiration', @date_type, '', '',
- ],
- 'primary_key' => 'prefnum',
- 'unique' => [],
- 'index' => [ [ 'usernum' ] ],
- },
-
- 'access_group' => {
- 'columns' => [
- 'groupnum', 'serial', '', '', '', '',
- 'groupname', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'groupnum',
- 'unique' => [ [ 'groupname' ] ],
- 'index' => [],
- },
-
- 'access_usergroup' => {
- 'columns' => [
- 'usergroupnum', 'serial', '', '', '', '',
- 'usernum', 'int', '', '', '', '',
- 'groupnum', 'int', '', '', '', '',
- ],
- 'primary_key' => 'usergroupnum',
- 'unique' => [ [ 'usernum', 'groupnum' ] ],
- 'index' => [ [ 'usernum' ] ],
- },
-
- 'access_groupagent' => {
- 'columns' => [
- 'groupagentnum', 'serial', '', '', '', '',
- 'groupnum', 'int', '', '', '', '',
- 'agentnum', 'int', '', '', '', '',
- ],
- 'primary_key' => 'groupagentnum',
- 'unique' => [ [ 'groupnum', 'agentnum' ] ],
- 'index' => [ [ 'groupnum' ] ],
- },
-
- 'access_right' => {
- 'columns' => [
- 'rightnum', 'serial', '', '', '', '',
- 'righttype', 'varchar', '', $char_d, '', '',
- 'rightobjnum', 'int', '', '', '', '',
- 'rightname', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'rightnum',
- 'unique' => [ [ 'righttype', 'rightobjnum', 'rightname' ] ],
- 'index' => [],
- },
-
- 'svc_phone' => {
- 'columns' => [
- 'svcnum', 'int', '', '', '', '',
- 'countrycode', 'varchar', '', 3, '', '',
- 'phonenum', 'varchar', '', 15, '', '', #12 ?
- 'pin', 'varchar', 'NULL', $char_d, '', '',
- ],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [ [ 'countrycode', 'phonenum' ] ],
- },
-
- 'reason_type' => {
- 'columns' => [
- 'typenum', 'serial', '', '', '', '',
- 'class', 'char', '', 1, '', '',
- 'type', 'varchar', '', $char_d, '', '',
- ],
- 'primary_key' => 'typenum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'reason' => {
- 'columns' => [
- 'reasonnum', 'serial', '', '', '', '',
- 'reason_type', 'int', '', '', '', '',
- 'reason', 'text', '', '', '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- ],
- 'primary_key' => 'reasonnum',
- 'unique' => [],
- 'index' => [],
- },
-
- 'conf' => {
- 'columns' => [
- 'confnum', 'serial', '', '', '', '',
- 'agentnum', 'int', 'NULL', '', '', '',
- 'name', 'varchar', '', $char_d, '', '',
- 'value', 'text', 'NULL', '', '', '',
- ],
- 'primary_key' => 'confnum',
- 'unique' => [ [ 'agentnum', 'name' ]],
- 'index' => [],
- },
-
- 'pkg_referral' => {
- 'columns' => [
- 'pkgrefnum', 'serial', '', '', '', '',
- 'pkgnum', 'int', '', '', '', '',
- 'refnum', 'int', '', '', '', '',
- ],
- 'primary_key' => 'pkgrefnum',
- 'unique' => [ [ 'pkgnum', 'refnum' ] ],
- 'index' => [ [ 'pkgnum' ], [ 'refnum' ] ],
- },
- # name type nullability length default local
-
- #'new_table' => {
- # 'columns' => [
- # 'num', 'serial', '', '', '', '',
- # ],
- # 'primary_key' => 'num',
- # 'unique' => [],
- # 'index' => [],
- #},
-
- };
-
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<DBIx::DBSchema>
-
-=cut
-
-1;
-
diff --git a/FS/FS/SearchCache.pm b/FS/FS/SearchCache.pm
deleted file mode 100644
index 4218acf..0000000
--- a/FS/FS/SearchCache.pm
+++ /dev/null
@@ -1,96 +0,0 @@
-package FS::SearchCache;
-
-use strict;
-use vars qw($DEBUG);
-#use Carp qw(carp cluck croak confess);
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::SearchCache - cache
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-=head1 METHODS
-
-=over 4
-
-=item new
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my( $table, $key ) = @_;
- warn "table $table\n" if $DEBUG > 1;
- warn "key $key\n" if $DEBUG > 1;
- my $self = { 'table' => $table,
- 'key' => $key,
- 'cache' => {},
- 'subcache' => {},
- };
- bless ($self, $class);
-
- $self;
-}
-
-=item table
-
-=cut
-
-sub table { my $self = shift; $self->{table}; }
-
-=item key
-
-=cut
-
-sub key { my $self = shift; $self->{key}; }
-
-=item cache
-
-=cut
-
-sub cache { my $self = shift; $self->{cache}; }
-
-=item subcache
-
-=cut
-
-sub subcache {
- my $self = shift;
- my $col = shift;
- my $table = shift;
- my $keyval = shift;
- if ( exists $self->{subcache}->{$col}->{$keyval} ) {
- warn "returning existing subcache for $keyval ($col)".
- "$self->{subcache}->{$col}->{$keyval}\n" if $DEBUG;
- return $self->{subcache}->{$col}->{$keyval};
- } else {
- #my $tablekey = @_ ? shift : $col;
- my $tablekey = $col;
- my $subcache = ref($self)->new( $table, $tablekey );
- $self->{subcache}->{$col}->{$keyval} = $subcache;
- warn "creating new subcache $table $tablekey: $subcache\n" if $DEBUG;
- $subcache;
- }
-}
-
-=back
-
-=head1 BUGS
-
-Dismal documentation.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_main>
-
-=cut
-
-1;
-
-
diff --git a/FS/FS/Setup.pm b/FS/FS/Setup.pm
deleted file mode 100644
index d265d93..0000000
--- a/FS/FS/Setup.pm
+++ /dev/null
@@ -1,525 +0,0 @@
-package FS::Setup;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK );
-use Exporter;
-#use Tie::DxHash;
-use Tie::IxHash;
-use FS::UID qw( dbh driver_name );
-use FS::Record;
-
-use FS::svc_domain;
-$FS::svc_domain::whois_hack = 1;
-$FS::svc_domain::whois_hack = 1;
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw( create_initial_data );
-
-=head1 NAME
-
-FS::Setup - Database setup
-
-=head1 SYNOPSIS
-
- use FS::Setup;
-
-=head1 DESCRIPTION
-
-Currently this module simply provides a place to store common subroutines for
-database setup.
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item
-
-=cut
-
-sub create_initial_data {
- my %opt = @_;
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- $FS::UID::AutoCommit = 0;
-
- populate_locales();
-
- populate_duplock();
-
- #initial_data data
- populate_initial_data(%opt);
-
- populate_access();
-
- populate_msgcat();
-
- if ( $oldAutoCommit ) {
- dbh->commit or die dbh->errstr;
- }
-
-}
-
-sub populate_locales {
-
- use Locale::Country;
- use FS::cust_main_county;
-
- #cust_main_county
- foreach my $country ( sort map uc($_), all_country_codes ) {
- _add_country($country);
- }
-
-}
-
-sub populate_addl_locales {
-
- my %addl = (
- 'US' => {
- 'FM' => 'Federated States of Micronesia',
- 'MH' => 'Marshall Islands',
- 'PW' => 'Palau',
- 'AA' => "Armed Forces Americas (except Canada)",
- 'AE' => "Armed Forces Europe / Canada / Middle East / Africa",
- 'AP' => "Armed Forces Pacific",
- },
- );
-
- foreach my $country ( keys %addl ) {
- foreach my $state ( keys %{ $addl{$country} } ) {
- # $longname = $addl{$country}{$state};
- _add_locale( 'country'=>$country, 'state'=>$state);
- }
- }
-
-}
-
-sub _add_country {
-
- use Locale::SubCountry;
-
- my( $country ) = shift;
-
- my $subcountry = eval { new Locale::SubCountry($country) };
- my @states = $subcountry ? $subcountry->all_codes : undef;
-
- if ( !scalar(@states) || ( scalar(@states)==1 && !defined($states[0]) ) ) {
-
- _add_locale( 'country'=>$country );
-
- } else {
-
- if ( $states[0] =~ /^(\d+|\w)$/ ) {
- @states = map $subcountry->full_name($_), @states
- }
-
- foreach my $state ( @states ) {
- _add_locale( 'country'=>$country, 'state'=>$state);
- }
-
- }
-
-}
-
-sub _add_locale {
- my $cust_main_county = new FS::cust_main_county( { 'tax'=>0, @_ });
- my $error = $cust_main_county->insert;
- die $error if $error;
-}
-
-sub populate_duplock {
-
- return unless driver_name =~ /^mysql/i;
-
- my $sth = dbh->prepare(
- "INSERT INTO duplicate_lock ( lockname ) VALUES ( 'svc_acct' )"
- ) or die dbh->errstr;
-
- $sth->execute or die $sth->errstr;
-
-}
-
-sub populate_initial_data {
- my %opt = @_;
-
- my $data = initial_data(%opt);
-
- foreach my $table ( keys %$data ) {
-
- my $class = "FS::$table";
- eval "use $class;";
- die $@ if $@;
-
- $class->_populate_initial_data(%opt)
- if $class->can('_populate_inital_data');
-
- my @records = @{ $data->{$table} };
-
- foreach my $record ( @records ) {
- my $args = delete($record->{'_insert_args'}) || [];
- my $object = $class->new( $record );
- my $error = $object->insert( @$args );
- die "error inserting record into $table: $error\n"
- if $error;
- }
-
- }
-
-}
-
-sub initial_data {
- my %opt = @_;
-
- #tie my %hash, 'Tie::DxHash',
- tie my %hash, 'Tie::IxHash',
-
- #superuser group
- 'access_group' => [
- { 'groupname' => 'Superuser' },
- ],
-
- #reason types
- 'reason_type' => [],
-
-#XXX need default new-style billing events
-# #billing events
-# 'part_bill_event' => [
-# { 'payby' => 'CARD',
-# 'event' => 'Batch card',
-# 'seconds' => 0,
-# 'eventcode' => '$cust_bill->batch_card(%options);',
-# 'weight' => 40,
-# 'plan' => 'batch-card',
-# },
-# { 'payby' => 'BILL',
-# 'event' => 'Send invoice',
-# 'seconds' => 0,
-# 'eventcode' => '$cust_bill->send();',
-# 'weight' => 50,
-# 'plan' => 'send',
-# },
-# { 'payby' => 'DCRD',
-# 'event' => 'Send invoice',
-# 'seconds' => 0,
-# 'eventcode' => '$cust_bill->send();',
-# 'weight' => 50,
-# 'plan' => 'send',
-# },
-# { 'payby' => 'DCHK',
-# 'event' => 'Send invoice',
-# 'seconds' => 0,
-# 'eventcode' => '$cust_bill->send();',
-# 'weight' => 50,
-# 'plan' => 'send',
-# },
-# { 'payby' => 'DCLN',
-# 'event' => 'Suspend',
-# 'seconds' => 0,
-# 'eventcode' => '$cust_bill->suspend();',
-# 'weight' => 40,
-# 'plan' => 'suspend',
-# },
-# #{ 'payby' => 'DCLN',
-# # 'event' => 'Retriable',
-# # 'seconds' => 0,
-# # 'eventcode' => '$cust_bill_event->retriable();',
-# # 'weight' => 60,
-# # 'plan' => 'retriable',
-# #},
-# ],
-
- #you must create a service definition. An example of a service definition
- #would be a dial-up account or a domain. First, it is necessary to create a
- #domain definition. Click on View/Edit service definitions and Add a new
- #service definition with Table svc_domain (and no modifiers).
- 'part_svc' => [
- { 'svc' => 'Domain',
- 'svcdb' => 'svc_domain',
- }
- ],
-
- #Now that you have created your first service, you must create a package
- #including this service which you can sell to customers. Zero, one, or many
- #services are bundled into a package. Click on View/Edit package
- #definitions and Add a new package definition which includes quantity 1 of
- #the svc_domain service you created above.
- 'part_pkg' => [
- { 'pkg' => 'System Domain',
- 'comment' => '(NOT FOR CUSTOMERS)',
- 'freq' => '0',
- 'plan' => 'flat',
- '_insert_args' => [
- 'pkg_svc' => { 1 => 1 }, # XXX
- 'primary_svc' => 1, #XXX
- 'options' => {
- 'setup_fee' => '0',
- 'recur_fee' => '0',
- },
- ],
- },
- ],
-
- #After you create your first package, then you must define who is able to
- #sell that package by creating an agent type. An example of an agent type
- #would be an internal sales representitive which sells regular and
- #promotional packages, as opposed to an external sales representitive
- #which would only sell regular packages of services. Click on View/Edit
- #agent types and Add a new agent type.
- 'agent_type' => [
- { 'atype' => 'internal' },
- ],
-
- #Allow this agent type to sell the package you created above.
- 'type_pkgs' => [
- { 'typenum' => 1, #XXX
- 'pkgpart' => 1, #XXX
- },
- ],
-
- #After creating a new agent type, you must create an agent. Click on
- #View/Edit agents and Add a new agent.
- 'agent' => [
- { 'agent' => 'Internal',
- 'typenum' => 1, # XXX
- },
- ],
-
- #Set up at least one Advertising source. Advertising sources will help you
- #keep track of how effective your advertising is, tracking where customers
- #heard of your service offerings. You must create at least one advertising
- #source. If you do not wish to use the referral functionality, simply
- #create a single advertising source only. Click on View/Edit advertising
- #sources and Add a new advertising source.
- 'part_referral' => [
- { 'referral' => 'Internal', },
- ],
-
- #Click on New Customer and create a new customer for your system accounts
- #with billing type Complimentary. Leave the First package dropdown set to
- #(none).
- 'cust_main' => [
- { 'agentnum' => 1, #XXX
- 'refnum' => 1, #XXX
- 'first' => 'System',
- 'last' => 'Accounts',
- 'address1' => '1234 System Lane',
- 'city' => 'Systemtown',
- 'state' => 'CA',
- 'zip' => '54321',
- 'country' => 'US',
- 'payby' => 'COMP',
- 'payinfo' => 'system', #or something
- 'paydate' => '1/2037',
- },
- ],
-
- #From the Customer View screen of the newly created customer, order the
- #package you defined above.
- 'cust_pkg' => [
- { 'custnum' => 1, #XXX
- 'pkgpart' => 1, #XXX
- },
- ],
-
- #From the Package View screen of the newly created package, choose
- #(Provision) to add the customer's service for this new package.
- #Add your own domain.
- 'svc_domain' => [
- { 'domain' => $opt{'domain'},
- 'pkgnum' => 1, #XXX
- 'svcpart' => 1, #XXX
- 'action' => 'N', #pseudo-field
- },
- ],
-
- #Go back to View/Edit service definitions on the main menu, and Add a new
- #service definition with Table svc_acct. Select your domain in the domsvc
- #Modifier. Set Fixed to define a service locked-in to this domain, or
- #Default to define a service which may select from among this domain and
- #the customer's domains.
-
- #not yet....
-
- #)
- ;
-
- \%hash;
-
-}
-
-sub populate_access {
-
- use FS::AccessRight;
- use FS::access_right;
-
- foreach my $rightname ( FS::AccessRight->rights ) {
- my $access_right = new FS::access_right {
- 'righttype' => 'FS::access_group',
- 'rightobjnum' => 1, #$supergroup->groupnum,
- 'rightname' => $rightname,
- };
- my $ar_error = $access_right->insert;
- die $ar_error if $ar_error;
- }
-
- #foreach my $agent ( qsearch('agent', {} ) ) {
- my $access_groupagent = new FS::access_groupagent {
- 'groupnum' => 1, #$supergroup->groupnum,
- 'agentnum' => 1, #$agent->agentnum,
- };
- my $aga_error = $access_groupagent->insert;
- die $aga_error if $aga_error;
- #}
-
-}
-
-sub populate_msgcat {
-
- use FS::Record qw(qsearch);
- use FS::msgcat;
-
- foreach my $del_msgcat ( qsearch('msgcat', {}) ) {
- my $error = $del_msgcat->delete;
- die $error if $error;
- }
-
- my %messages = msgcat_messages();
-
- foreach my $msgcode ( keys %messages ) {
- foreach my $locale ( keys %{$messages{$msgcode}} ) {
- my $msgcat = new FS::msgcat( {
- 'msgcode' => $msgcode,
- 'locale' => $locale,
- 'msg' => $messages{$msgcode}{$locale},
- });
- my $error = $msgcat->insert;
- die $error if $error;
- }
- }
-
-}
-
-sub msgcat_messages {
-
- # 'msgcode' => {
- # 'en_US' => 'Message',
- # },
-
- (
-
- 'passwords_dont_match' => {
- 'en_US' => "Passwords don't match",
- },
-
- 'invalid_card' => {
- 'en_US' => 'Invalid credit card number',
- },
-
- 'unknown_card_type' => {
- 'en_US' => 'Unknown card type',
- },
-
- 'not_a' => {
- 'en_US' => 'Not a ',
- },
-
- 'empty_password' => {
- 'en_US' => 'Empty password',
- },
-
- 'no_access_number_selected' => {
- 'en_US' => 'No access number selected',
- },
-
- 'illegal_text' => {
- 'en_US' => 'Illegal (text)',
- #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in field',
- },
-
- 'illegal_or_empty_text' => {
- 'en_US' => 'Illegal or empty (text)',
- #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in required field',
- },
-
- 'illegal_username' => {
- 'en_US' => 'Illegal username',
- },
-
- 'illegal_password' => {
- 'en_US' => 'Illegal password (',
- },
-
- 'illegal_password_characters' => {
- 'en_US' => ' characters)',
- },
-
- 'username_in_use' => {
- 'en_US' => 'Username in use',
- },
-
- 'illegal_email_invoice_address' => {
- 'en_US' => 'Illegal email invoice address',
- },
-
- 'illegal_name' => {
- 'en_US' => 'Illegal (name)',
- #'en_US' => 'Only letters, numbers, spaces and the following punctuation symbols are permitted: , . - \' in field',
- },
-
- 'illegal_phone' => {
- 'en_US' => 'Illegal (phone)',
- #'en_US' => '',
- },
-
- 'illegal_zip' => {
- 'en_US' => 'Illegal (zip)',
- #'en_US' => '',
- },
-
- 'expired_card' => {
- 'en_US' => 'Expired card',
- },
-
- 'daytime' => {
- 'en_US' => 'Day Phone',
- },
-
- 'night' => {
- 'en_US' => 'Night Phone',
- },
-
- 'svc_external-id' => {
- 'en_US' => 'External ID',
- },
-
- 'svc_external-title' => {
- 'en_US' => 'Title',
- },
-
- 'stateid' => {
- 'en_US' => 'Driver\'s License',
- },
-
- 'stateid_state' => {
- 'en_US' => 'Driver\'s License State',
- },
-
- 'invalid_domain' => {
- 'en_US' => 'Invalid domain',
- },
-
- );
-}
-
-=back
-
-=head1 BUGS
-
-Sure.
-
-=head1 SEE ALSO
-
-=cut
-
-1;
-
diff --git a/FS/FS/TicketSystem.pm b/FS/FS/TicketSystem.pm
deleted file mode 100644
index a80a827..0000000
--- a/FS/FS/TicketSystem.pm
+++ /dev/null
@@ -1,30 +0,0 @@
-package FS::TicketSystem;
-
-use strict;
-use vars qw( $conf $system $AUTOLOAD );
-use FS::Conf;
-use FS::UID;
-
-FS::UID->install_callback( sub {
- $conf = new FS::Conf;
- $system = $conf->config('ticket_system');
-} );
-
-sub AUTOLOAD {
- my $self = shift;
-
- my($sub)=$AUTOLOAD;
- $sub =~ s/.*://;
-
- my $conf = new FS::Conf;
- die "FS::TicketSystem::$AUTOLOAD called, but no ticket system configured\n"
- unless $system;
-
- eval "use FS::TicketSystem::$system;";
- die $@ if $@;
-
- $self .= "::$system";
- $self->$sub(@_);
-}
-
-1;
diff --git a/FS/FS/TicketSystem/RT_External.pm b/FS/FS/TicketSystem/RT_External.pm
deleted file mode 100644
index 3a9c7e8..0000000
--- a/FS/FS/TicketSystem/RT_External.pm
+++ /dev/null
@@ -1,353 +0,0 @@
-package FS::TicketSystem::RT_External;
-
-use strict;
-use vars qw( $DEBUG $me $conf $dbh $default_queueid $external_url
- $priority_reverse
- $priority_field $priority_field_queue $field
- );
-use URI::Escape;
-use FS::UID qw(dbh);
-use FS::Record qw(qsearchs);
-use FS::cust_main;
-
-$me = '[FS::TicketSystem::RT_External]';
-$DEBUG = 0;
-
-FS::UID->install_callback( sub {
- $conf = new FS::Conf;
- $default_queueid = $conf->config('ticket_system-default_queueid');
- $priority_reverse = $conf->exists('ticket_system-priority_reverse');
- $priority_field =
- $conf->config('ticket_system-custom_priority_field');
- if ( $priority_field ) {
- $priority_field_queue =
- $conf->config('ticket_system-custom_priority_field_queue');
-
- $field = $priority_field_queue
- ? $priority_field_queue. '.%7B'. $priority_field. '%7D'
- : $priority_field;
- } else {
- $priority_field_queue = '';
- $field = '';
- }
-
- $external_url = '';
- $dbh = dbh;
- if ($conf->config('ticket_system') eq 'RT_External') {
- my ($datasrc, $user, $pass) = $conf->config('ticket_system-rt_external_datasrc');
- $dbh = DBI->connect($datasrc, $user, $pass, { 'ChopBlanks' => 1 })
- or die "RT_External DBI->connect error: $DBI::errstr\n";
-
- $external_url = $conf->config('ticket_system-rt_external_url');
- }
-
- #kludge... should *use* the id... but good enough for now
- if ( $priority_field_queue =~ /^(\d+)$/ ) {
- my $id = $1;
- my $sql = 'SELECT Name FROM Queues WHERE Id = ?';
- my $sth = $dbh->prepare($sql) or die $dbh->errstr. " preparing $sql";
- $sth->execute($id) or die $sth->errstr. " executing $sql";
-
- $priority_field_queue = $sth->fetchrow_arrayref->[0];
-
- }
-
-} );
-
-sub num_customer_tickets {
- my( $self, $custnum, $priority ) = @_;
-
- my( $from_sql, @param) = $self->_from_customer( $custnum, $priority );
-
- my $sql = "SELECT COUNT(*) $from_sql";
- warn "$me $sql (@param)" if $DEBUG;
- my $sth = $dbh->prepare($sql) or die $dbh->errstr. " preparing $sql";
- $sth->execute(@param) or die $sth->errstr. " executing $sql";
-
- $sth->fetchrow_arrayref->[0];
-
-}
-
-sub customer_tickets {
- my( $self, $custnum, $limit, $priority ) = @_;
- $limit ||= 0;
-
- my( $from_sql, @param) = $self->_from_customer( $custnum, $priority );
- my $sql = "
- SELECT Tickets.*,
- Queues.Name AS Queue,
- Users.Name AS Owner,
- position(Tickets.Status in 'newopenstalledresolvedrejecteddeleted')
- AS svalue
- ". ( length($priority) ? ", ObjectCustomFieldValues.Content" : '' )."
- $from_sql
- ORDER BY svalue,
- Priority ". ( $priority_reverse ? 'ASC' : 'DESC' ). ",
- id DESC
- LIMIT $limit
- ";
- warn "$me $sql (@param)" if $DEBUG;
- my $sth = $dbh->prepare($sql) or die $dbh->errstr. "preparing $sql";
- $sth->execute(@param) or die $sth->errstr. "executing $sql";
-
- #munge column names??? #httemplate/view/cust_main/tickets.html has column
- #names that might not make sense now...
- $sth->fetchall_arrayref({});
-
-}
-
-sub _from_customer {
- my( $self, $custnum, $priority ) = @_;
-
- my @param = ();
- my $join = '';
- my $where = '';
- if ( defined($priority) ) {
-
- my $queue_sql = " ObjectCustomFields.ObjectId = ( SELECT id FROM Queues
- WHERE Queues.Name = ? )
- OR ( ? = '' AND ObjectCustomFields.ObjectId = 0 )";
-
- my $customfield_sql =
- "customfield = (
- SELECT CustomFields.Id FROM CustomFields
- JOIN ObjectCustomFields
- ON ( CustomFields.id = ObjectCustomFields.CustomField )
- WHERE LookupType = 'RT::Queue-RT::Ticket'
- AND Name = ?
- AND ( $queue_sql )
- )";
-
- push @param, $priority_field,
- $priority_field_queue,
- $priority_field_queue;
-
- if ( length($priority) ) {
- #$where = "
- # and ? = ( select content from TicketCustomFieldValues
- # where ticket = tickets.id
- # and customfield = ( select id from customfields
- # where name = ?
- # and ( $queue_sql )
- # )
- # )
- #";
- unshift @param, $priority;
-
- $join = "JOIN ObjectCustomFieldValues
- ON ( Tickets.id = ObjectCustomFieldValues.ObjectId )";
-
- $where = " AND Content = ?
- AND ObjectCustomFieldValues.Disabled != 1
- AND ObjectType = 'RT::Ticket'
- AND $customfield_sql";
-
- } else {
-
- $where =
- "AND 0 = ( SELECT COUNT(*) FROM ObjectCustomFieldValues
- WHERE ObjectId = Tickets.id
- AND ObjectType = 'RT::Ticket'
- AND $customfield_sql
- )
- ";
- }
-
- }
-
- my $sql = "
- FROM Tickets
- JOIN Queues ON ( Tickets.Queue = Queues.id )
- JOIN Links ON ( Tickets.id = Links.LocalBase )
- JOIN Users ON ( Tickets.Owner = Users.id )
- $join
- WHERE ( ". join(' OR ', map "Status = '$_'", $self->statuses ). " )
- AND Target = 'freeside://freeside/cust_main/$custnum'
- $where
- ";
-
- ( $sql, @param );
-
-}
-
-sub statuses {
- #my $self = shift;
- my @statuses = grep { ! /^\s*$/ } $conf->config('cust_main-ticket_statuses');
- @statuses = (qw( new open stalled )) unless scalar(@statuses);
- @statuses;
-}
-
-sub href_customer_tickets {
- my( $self, $custnum ) = ( shift, shift );
- my( $priority, @statuses);
- if ( ref($_[0]) ) {
- my $opt = shift;
- $priority = $opt->{'priority'};
- @statuses = $opt->{'statuses'} ? @{$opt->{'statuses'}} : $self->statuses;
- } else {
- $priority = shift;
- @statuses = $self->statuses;
- }
-
- #my $href = $self->baseurl;
-
- #i snarfed this from an RT bookmarked search, then unescaped (some of) it with
- #perl -npe 's/%([0-9A-F]{2})/pack('C', hex($1))/eg;'
-
- #$href .=
- my $href =
- "Search/Results.html?Order=ASC&".
- "Query= MemberOf = 'freeside://freeside/cust_main/$custnum' ".
- #" AND ( Status = 'open' OR Status = 'new' OR Status = 'stalled' )"
- " AND ( ". join(' OR ', map "Status = '$_'", @statuses ). " ) "
- ;
-
- if ( defined($priority) && $field && $priority_field_queue ) {
- $href .= " AND Queue = '$priority_field_queue' ";
- }
- if ( defined($priority) && $field ) {
- $href .= " AND 'CF.$field' ";
- if ( $priority ) {
- $href .= "= '$priority' ";
- } else {
- $href .= "IS 'NULL' "; #this is "RTQL", not SQL
- }
- }
-
- #$href =
- uri_escape($href);
- #eventually should unescape all of it...
-
- $href .= '&Rows=100'.
- '&OrderBy=id&Page=1'.
- '&Format=%27%20%20%20%3Cb%3E%3Ca%20href%3D%22'.
- $self->baseurl.
- 'Ticket%2FDisplay.html%3Fid%3D__id__%22%3E__id__%3C%2Fa%3E%3C%2Fb%3E%2FTITLE%3A%23%27%2C%20%0A%27%3Cb%3E%3Ca%20href%3D%22'.
- $self->baseurl.
- 'Ticket%2FDisplay.html%3Fid%3D__id__%22%3E__Subject__%3C%2Fa%3E%3C%2Fb%3E%2FTITLE%3ASubject%27%2C%20%0A%27__Status__%27%2C%20';
-
- if ( defined($priority) && $field ) {
- $href .= '%0A%27__CustomField.'. $field. '__%2FTITLE%3ASeverity%27%2C%20';
- }
-
- $href .= '%0A%27__QueueName__%27%2C%20%0A%27__OwnerName__%27%2C%20%0A%27__Priority__%27%2C%20%0A%27__NEWLINE__%27%2C%20%0A%27%27%2C%20%0A%27%3Csmall%3E__Requestors__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__CreatedRelative__%3C%2Fsmall%3E%27%2C';
-
- if ( defined($priority) && $field ) {
- $href .= '%20%0A%27__-__%27%2C';
- }
-
- $href .= '%20%0A%27%3Csmall%3E__ToldRelative__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__LastUpdatedRelative__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__TimeLeft__%3C%2Fsmall%3E%27';
-
- #$href =
- #uri_escape($href);
-
- $self->baseurl. $href;
-
-}
-
-sub href_new_ticket {
- my( $self, $custnum_or_cust_main, $requestors ) = @_;
-
- my( $custnum, $cust_main );
- if ( ref($custnum_or_cust_main) ) {
- $cust_main = $custnum_or_cust_main;
- $custnum = $cust_main->custnum;
- } else {
- $custnum = $custnum_or_cust_main;
- $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
- }
- my $queueid = $cust_main->agent->ticketing_queueid || $default_queueid;
-
- $self->baseurl.
- 'Ticket/Create.html?'.
- "Queue=$queueid".
- "&new-MemberOf=freeside://freeside/cust_main/$custnum".
- ( $requestors ? '&Requestors='. uri_escape($requestors) : '' )
- ;
-}
-
-sub href_ticket {
- my($self, $ticketnum) = @_;
- $self->baseurl. 'Ticket/Display.html?id='.$ticketnum;
-}
-
-sub queues {
- my($self) = @_;
-
- my $sql = "SELECT id, Name FROM Queues WHERE Disabled = 0";
- my $sth = $dbh->prepare($sql) or die $dbh->errstr. " preparing $sql";
- $sth->execute() or die $sth->errstr. " executing $sql";
-
- map { $_->[0] => $_->[1] } @{ $sth->fetchall_arrayref([]) };
-
-}
-
-sub queue {
- my($self, $queueid) = @_;
-
- return '' unless $queueid;
-
- my $sql = "SELECT Name FROM Queues WHERE id = ?";
- my $sth = $dbh->prepare($sql) or die $dbh->errstr. " preparing $sql";
- $sth->execute($queueid) or die $sth->errstr. " executing $sql";
-
- my $rows = $sth->fetchrow_arrayref;
- $rows ? $rows->[0] : '';
-
-}
-
-sub baseurl {
- #my $self = shift;
- $external_url. '/';
-}
-
-sub _retrieve_single_value {
- my( $self, $sql ) = @_;
-
- warn "$me $sql" if $DEBUG;
- my $sth = $dbh->prepare($sql) or die $dbh->errstr. "preparing $sql";
- $sth->execute or die $sth->errstr. "executing $sql";
-
- my $arrayref = $sth->fetchrow_arrayref;
- $arrayref ? $arrayref->[0] : $arrayref;
-}
-
-sub transaction_creator {
- my( $self, $transaction_id ) = @_;
-
- my $sql = "SELECT Name FROM Transactions JOIN Users ON ".
- "Transactions.Creator=Users.id WHERE Transactions.id = ".
- $transaction_id;
-
- $self->_retrieve_single_value($sql);
-}
-
-sub transaction_ticketid {
- my( $self, $transaction_id ) = @_;
-
- my $sql = "SELECT ObjectId FROM Transactions WHERE Transactions.id = ".
- $transaction_id;
-
- $self->_retrieve_single_value($sql);
-}
-
-sub transaction_subject {
- my( $self, $transaction_id ) = @_;
-
- my $sql = "SELECT Subject FROM Transactions JOIN Tickets ON ObjectId=".
- "Tickets.id WHERE Transactions.id = ". $transaction_id;
-
- $self->_retrieve_single_value($sql);
-}
-
-sub transaction_status {
- my( $self, $transaction_id ) = @_;
-
- my $sql = "SELECT Status FROM Transactions JOIN Tickets ON ObjectId=".
- "Tickets.id WHERE Transactions.id = ". $transaction_id;
-
- $self->_retrieve_single_value($sql);
-}
-
-1;
-
diff --git a/FS/FS/TicketSystem/RT_Internal.pm b/FS/FS/TicketSystem/RT_Internal.pm
deleted file mode 100644
index d24a96c..0000000
--- a/FS/FS/TicketSystem/RT_Internal.pm
+++ /dev/null
@@ -1,29 +0,0 @@
-package FS::TicketSystem::RT_Internal;
-
-use strict;
-use vars qw( @ISA );
-use FS::UID qw(dbh);
-use FS::CGI qw(popurl);
-use FS::TicketSystem::RT_Libs;
-
-@ISA = qw( FS::TicketSystem::RT_Libs );
-
-sub sql_num_customer_tickets {
- "( select count(*) from tickets
- join links on ( tickets.id = links.localbase )
- where ( status = 'new' or status = 'open' or status = 'stalled' )
- and target = 'freeside://freeside/cust_main/' || custnum
- )";
-}
-
-sub baseurl {
- #my $self = shift;
- if ( $RT::URI::freeside::URL ) {
- $RT::URI::freeside::URL. '/rt/';
- } else {
- 'http://you_need_to_set_RT_URI_freeside_URL_in_SiteConfig.pm/';
- }
-}
-
-1;
-
diff --git a/FS/FS/TicketSystem/RT_Libs.pm b/FS/FS/TicketSystem/RT_Libs.pm
deleted file mode 100644
index aebe8c5..0000000
--- a/FS/FS/TicketSystem/RT_Libs.pm
+++ /dev/null
@@ -1,10 +0,0 @@
-package FS::TicketSystem::RT_Libs;
-
-use strict;
-use vars qw( @ISA );
-use FS::TicketSystem::RT_External;
-
-@ISA = qw( FS::TicketSystem::RT_External );
-
-1;
-
diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm
deleted file mode 100644
index e4a9ac1..0000000
--- a/FS/FS/UI/Web.pm
+++ /dev/null
@@ -1,573 +0,0 @@
-package FS::UI::Web;
-
-use strict;
-use vars qw($DEBUG @ISA @EXPORT_OK $me);
-use Exporter;
-use FS::Conf;
-use FS::Record qw(dbdef);
-use FS::cust_main; # are sql_balance and sql_date_balance in the right module?
-
-#use vars qw(@ISA);
-#use FS::UI
-#@ISA = qw( FS::UI );
-@ISA = qw( Exporter );
-
-@EXPORT_OK = qw( svc_url );
-
-$DEBUG = 0;
-$me = '[FS::UID::Web]';
-
-###
-# date parsing
-###
-
-use Date::Parse;
-sub parse_beginning_ending {
- my($cgi, $prefix) = @_;
- $prefix .= '_' if $prefix;
-
- my $beginning = 0;
- if ( $cgi->param($prefix.'begin') =~ /^(\d+)$/ ) {
- $beginning = $1;
- } elsif ( $cgi->param($prefix.'beginning') =~ /^([ 0-9\-\/]{1,64})$/ ) {
- $beginning = str2time($1) || 0;
- }
-
- my $ending = 4294967295; #2^32-1
- if ( $cgi->param($prefix.'end') =~ /^(\d+)$/ ) {
- $ending = $1 - 1;
- } elsif ( $cgi->param($prefix.'ending') =~ /^([ 0-9\-\/]{1,64})$/ ) {
- #probably need an option to turn off the + 86399
- $ending = str2time($1) + 86399;
- }
-
- ( $beginning, $ending );
-}
-
-=item svc_url
-
-Returns a service URL, first checking to see if there is a service-specific
-page to link to, otherwise to a generic service handling page. Options are
-passed as a list of name-value pairs, and include:
-
-=over 4
-
-=item * m - Mason request object ($m)
-
-=item * action - The action for which to construct "edit", "view", or "search"
-
-=item ** part_svc - Service definition (see L<FS::part_svc>)
-
-=item ** svcdb - Service table
-
-=item *** query - Query string
-
-=item *** svc - FS::cust_svc or FS::svc_* object
-
-=item ahref - Optional flag, if set true returns <A HREF="$url"> instead of just the URL.
-
-=back
-
-* Required fields
-
-** part_svc OR svcdb is required
-
-*** query OR svc is required
-
-=cut
-
- # ##
- # #required
- # ##
- # 'm' => $m, #mason request object
- # 'action' => 'edit', #or 'view'
- #
- # 'part_svc' => $part_svc, #usual
- # #OR
- # 'svcdb' => 'svc_table',
- #
- # 'query' => #optional query string
- # # (pass a blank string if you want a "raw" URL to add your
- # # own svcnum to)
- # #OR
- # 'svc' => $svc_x, #or $cust_svc, it just needs a svcnum
- #
- # ##
- # #optional
- # ##
- # 'ahref' => 1, # if set true, returns <A HREF="$url">
-
-use FS::CGI qw(rooturl);
-sub svc_url {
- my %opt = @_;
-
- #? return '' unless ref($opt{part_svc});
-
- my $svcdb = $opt{svcdb} || $opt{part_svc}->svcdb;
- my $query = exists($opt{query}) ? $opt{query} : $opt{svc}->svcnum;
- my $url;
- warn "$me [svc_url] checking for /$opt{action}/$svcdb.cgi component"
- if $DEBUG;
- if ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.cgi") ) {
- $url = "$svcdb.cgi?";
- } else {
-
- my $generic = $opt{action} eq 'search' ? 'cust_svc' : 'svc_Common';
-
- $url = "$generic.html?svcdb=$svcdb;";
- $url .= 'svcnum=' if $query =~ /^\d+(;|$)/ or $query eq '';
- }
-
- import FS::CGI 'rooturl'; #WTF! why is this necessary
- my $return = rooturl(). "$opt{action}/$url$query";
-
- $return = qq!<A HREF="$return">! if $opt{ahref};
-
- $return;
-}
-
-sub svc_link {
- my($m, $part_svc, $cust_svc) = @_ or return '';
- svc_X_link( $part_svc->svc, @_ );
-}
-
-sub svc_label_link {
- my($m, $part_svc, $cust_svc) = @_ or return '';
- svc_X_link( ($cust_svc->label)[1], @_ );
-}
-
-sub svc_X_link {
- my ($x, $m, $part_svc, $cust_svc) = @_ or return '';
- my $ahref = svc_url(
- 'ahref' => 1,
- 'm' => $m,
- 'action' => 'view',
- 'part_svc' => $part_svc,
- 'svc' => $cust_svc,
- );
-
- "$ahref$x</A>";
-}
-
-sub svc_export_links {
- my ($m, $part_svc, $cust_svc) = @_ or return '';
-
- my $ahref = $cust_svc->export_links;
-
- join('', @$ahref);
-}
-
-sub parse_lt_gt {
- my($cgi, $field) = @_;
-
- my @search = ();
-
- my %op = (
- 'lt' => '<',
- 'gt' => '>',
- );
-
- foreach my $op (keys %op) {
-
- warn "checking for ${field}_$op field\n"
- if $DEBUG;
-
- if ( $cgi->param($field."_$op") =~ /^\s*\$?\s*(-?[\d\,\s]+(\.\d\d)?)\s*$/ ) {
-
- my $num = $1;
- $num =~ s/[\,\s]+//g;
- my $search = "$field $op{$op} $num";
- push @search, $search;
-
- warn "found ${field}_$op field; adding search element $search\n"
- if $DEBUG;
- }
-
- }
-
- @search;
-
-}
-
-###
-# cust_main report subroutines
-###
-
-
-=item cust_header [ CUST_FIELDS_VALUE ]
-
-Returns an array of customer information headers according to the supplied
-customer fields value, or if no value is supplied, the B<cust-fields>
-configuration value.
-
-=cut
-
-use vars qw( @cust_fields @cust_colors @cust_styles @cust_aligns );
-
-sub cust_header {
-
- warn "FS::UI:Web::cust_header called"
- if $DEBUG;
-
- my %header2method = (
- 'Customer' => 'name',
- 'Cust. Status' => 'ucfirst_cust_status',
- 'Cust#' => 'custnum',
- 'Name' => 'contact',
- 'Company' => 'company',
- '(bill) Customer' => 'name',
- '(service) Customer' => 'ship_name',
- '(bill) Name' => 'contact',
- '(service) Name' => 'ship_contact',
- '(bill) Company' => 'company',
- '(service) Company' => 'ship_company',
- 'Address 1' => 'address1',
- 'Address 2' => 'address2',
- 'City' => 'city',
- 'State' => 'state',
- 'Zip' => 'zip',
- 'Country' => 'country_full',
- 'Day phone' => 'daytime', # XXX should use msgcat, but how?
- 'Night phone' => 'night', # XXX should use msgcat, but how?
- 'Fax number' => 'fax',
- 'Invoicing email(s)' => 'invoicing_list_emailonly_scalar',
- 'Payment Type' => 'payby',
- 'Current Balance' => 'current_balance',
- );
-
- my %header2colormethod = (
- 'Cust. Status' => 'cust_statuscolor',
- );
- my %header2style = (
- 'Cust. Status' => 'b',
- );
- my %header2align = (
- 'Cust. Status' => 'c',
- );
-
- my $cust_fields;
- my @cust_header;
- if ( @_ && $_[0] ) {
-
- warn " using supplied cust-fields override".
- " (ignoring cust-fields config file)"
- if $DEBUG;
- $cust_fields = shift;
-
- } else {
-
- my $conf = new FS::Conf;
- if ( $conf->exists('cust-fields')
- && $conf->config('cust-fields') =~ /^([\w\. \|\#\(\)]+):?/
- )
- {
- warn " found cust-fields configuration value"
- if $DEBUG;
- $cust_fields = $1;
- } else {
- warn " no cust-fields configuration value found; using default 'Cust. Status | Customer'"
- if $DEBUG;
- $cust_fields = 'Cust. Status | Customer';
- }
-
- }
-
- @cust_header = split(/ \| /, $cust_fields);
- @cust_fields = map { $header2method{$_} } @cust_header;
- @cust_colors = map { exists $header2colormethod{$_}
- ? $header2colormethod{$_}
- : ''
- }
- @cust_header;
- @cust_styles = map { exists $header2style{$_} ? $header2style{$_} : '' }
- @cust_header;
- @cust_aligns = map { exists $header2align{$_} ? $header2align{$_} : 'l' }
- @cust_header;
-
- #my $svc_x = shift;
- @cust_header;
-}
-
-=item cust_sql_fields [ CUST_FIELDS_VALUE ]
-
-Returns a list of fields for the SELECT portion of an SQL query.
-
-As with L<the cust_header subroutine|/cust_header>, the fields returned are
-defined by the supplied customer fields setting, or if no customer fields
-setting is supplied, the <B>cust-fields</B> configuration value.
-
-=cut
-
-sub cust_sql_fields {
-
- my @fields = qw( last first company );
- push @fields, map "ship_$_", @fields;
- push @fields, 'country';
-
- cust_header(@_);
- #inefficientish, but tiny lists and only run once per page
- push @fields,
- grep { my $field = $_; grep { $_ eq $field } @cust_fields }
- qw( address1 address2 city state zip daytime night fax payby );
-
- my @extra_fields = ();
- if (grep { $_ eq 'current_balance' } @cust_fields) {
- push @extra_fields, FS::cust_main->balance_sql . " AS current_balance";
- }
-
- map("cust_main.$_", @fields), @extra_fields;
-}
-
-=item cust_fields OBJECT [ CUST_FIELDS_VALUE ]
-
-Given an object that contains fields from cust_main (say, from a
-JOINed search. See httemplate/search/svc_* for examples), returns an array
-of customer information, or "(unlinked)" if this service is not linked to a
-customer.
-
-As with L<the cust_header subroutine|/cust_header>, the fields returned are
-defined by the supplied customer fields setting, or if no customer fields
-setting is supplied, the <B>cust-fields</B> configuration value.
-
-=cut
-
-sub cust_fields {
- my $svc_x = shift;
- warn "FS::UI::Web::cust_fields called for $svc_x ".
- "(cust_fields: @cust_fields)"
- if $DEBUG > 1;
-
- #cust_header(@_) unless @cust_fields; #now need to cache to keep cust_fields
- # #override incase we were passed as a sub
-
- my $seen_unlinked = 0;
- map {
- if ( $svc_x->custnum ) {
- warn " $svc_x -> $_"
- if $DEBUG > 1;
- $svc_x->$_(@_);
- } else {
- warn " ($svc_x unlinked)"
- if $DEBUG > 1;
- $seen_unlinked++ ? '' : '(unlinked)';
- }
- } @cust_fields;
-}
-
-=item cust_colors
-
-Returns an array of subroutine references (or empty strings) for returning
-customer information colors.
-
-As with L<the cust_header subroutine|/cust_header>, the fields returned are
-defined by the supplied customer fields setting, or if no customer fields
-setting is supplied, the <B>cust-fields</B> configuration value.
-
-=cut
-
-sub cust_colors {
- map {
- my $method = $_;
- if ( $method ) {
- sub { shift->$method(@_) };
- } else {
- '';
- }
- } @cust_colors;
-}
-
-=item cust_styles
-
-Returns an array of customer information styles.
-
-As with L<the cust_header subroutine|/cust_header>, the fields returned are
-defined by the supplied customer fields setting, or if no customer fields
-setting is supplied, the <B>cust-fields</B> configuration value.
-
-=cut
-
-sub cust_styles {
- map {
- if ( $_ ) {
- $_;
- } else {
- '';
- }
- } @cust_styles;
-}
-
-=item cust_aligns
-
-Returns an array or scalar (depending on context) of customer information
-alignments.
-
-As with L<the cust_header subroutine|/cust_header>, the fields returned are
-defined by the supplied customer fields setting, or if no customer fields
-setting is supplied, the <B>cust-fields</B> configuration value.
-
-=cut
-
-sub cust_aligns {
- if ( wantarray ) {
- @cust_aligns;
- } else {
- join('', @cust_aligns);
- }
-}
-
-###
-# begin JSRPC code...
-###
-
-package FS::UI::Web::JSRPC;
-
-use strict;
-use vars qw($DEBUG);
-use Carp;
-use Storable qw(nfreeze);
-use MIME::Base64;
-use JSON;
-use FS::UID qw(getotaker);
-use FS::Record qw(qsearchs);
-use FS::queue;
-
-$DEBUG = 0;
-
-sub new {
- my $class = shift;
- my $self = {
- env => {},
- job => shift,
- cgi => shift,
- };
-
- bless $self, $class;
-
- croak "CGI object required as second argument" unless $self->{'cgi'};
-
- return $self;
-}
-
-sub process {
-
- my $self = shift;
-
- my $cgi = $self->{'cgi'};
-
- # XXX this should parse JSON foo and build a proper data structure
- my @args = $cgi->param('arg');
-
- #work around konqueror bug!
- @args = map { s/\x00$//; $_; } @args;
-
- my $sub = $cgi->param('sub'); #????
-
- warn "FS::UI::Web::JSRPC::process:\n".
- " cgi=$cgi\n".
- " sub=$sub\n".
- " args=".join(', ',@args)."\n"
- if $DEBUG;
-
- if ( $sub eq 'start_job' ) {
-
- $self->start_job(@args);
-
- } elsif ( $sub eq 'job_status' ) {
-
- $self->job_status(@args);
-
- } else {
-
- die "unknown sub $sub";
-
- }
-
-}
-
-sub start_job {
- my $self = shift;
-
- warn "FS::UI::Web::start_job: ". join(', ', @_) if $DEBUG;
-# my %param = @_;
- my %param = ();
- while ( @_ ) {
- my( $field, $value ) = splice(@_, 0, 2);
- unless ( exists( $param{$field} ) ) {
- $param{$field} = $value;
- } elsif ( ! ref($param{$field}) ) {
- $param{$field} = [ $param{$field}, $value ];
- } else {
- push @{$param{$field}}, $value;
- }
- }
- $param{CurrentUser} = getotaker();
- warn "FS::UI::Web::start_job\n".
- join('', map {
- if ( ref($param{$_}) ) {
- " $_ => [ ". join(', ', @{$param{$_}}). " ]\n";
- } else {
- " $_ => $param{$_}\n";
- }
- } keys %param )
- if $DEBUG;
-
- #first get the CGI params shipped off to a job ASAP so an id can be returned
- #to the caller
-
- my $job = new FS::queue { 'job' => $self->{'job'} };
-
- #too slow to insert all the cgi params as individual args..,?
- #my $error = $queue->insert('_JOB', $cgi->Vars);
-
- #warn 'froze string of size '. length(nfreeze(\%param)). " for job args\n"
- # if $DEBUG;
-
- my $error = $job->insert( '_JOB', encode_base64(nfreeze(\%param)) );
-
- if ( $error ) {
-
- warn "job not inserted: $error\n"
- if $DEBUG;
-
- $error; #this doesn't seem to be handled well,
- # will trigger "illegal jobnum" below?
- # (should never be an error inserting the job, though, only thing
- # would be Pg f%*kage)
- } else {
-
- warn "job inserted successfully with jobnum ". $job->jobnum. "\n"
- if $DEBUG;
-
- $job->jobnum;
- }
-
-}
-
-sub job_status {
- my( $self, $jobnum ) = @_; #$url ???
-
- sleep 1; # XXX could use something better...
-
- my $job;
- if ( $jobnum =~ /^(\d+)$/ ) {
- $job = qsearchs('queue', { 'jobnum' => $jobnum } );
- } else {
- die "FS::UI::Web::job_status: illegal jobnum $jobnum\n";
- }
-
- my @return;
- if ( $job && $job->status ne 'failed' ) {
- @return = ( 'progress', $job->statustext );
- } elsif ( !$job ) { #handle job gone case : job successful
- # so close popup, redirect parent window...
- @return = ( 'complete' );
- } else {
- @return = ( 'error', $job ? $job->statustext : $jobnum );
- }
-
- objToJson(\@return);
-
-}
-
-1;
-
diff --git a/FS/FS/UI/bytecount.pm b/FS/FS/UI/bytecount.pm
deleted file mode 100644
index d278dbe..0000000
--- a/FS/FS/UI/bytecount.pm
+++ /dev/null
@@ -1,96 +0,0 @@
-package FS::UI::bytecount;
-
-use strict;
-use vars qw($DEBUG $me);
-use FS::Conf;
-use Number::Format 1.50;
-
-$DEBUG = 0;
-$me = '[FS::UID::bytecount]';
-
-=head1 NAME
-
-FS::UI::bytecount - Subroutines for parsing and displaying byte counters
-
-=head1 SYNOPSIS
-
- use FS::UI::bytecount;
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item bytecount_unexact COUNT
-
-Returns a two decimal place value for COUNT followed by bytes, Kbytes, Mbytes,
-or GBytes as appropriate.
-
-=cut
-
-sub bytecount_unexact {
- my $bc = shift;
- return("$bc bytes")
- if ($bc < 1000);
- return(sprintf("%.2f Kbytes", $bc/1000))
- if ($bc < 1000000);
- return(sprintf("%.2f Mbytes", $bc/1000000))
- if ($bc < 1000000000);
- return(sprintf("%.2f Gbytes", $bc/1000000000));
-}
-
-=item parse_bytecount AMOUNT
-
-Accepts a number (digits and a decimal point) possibly followed by k, m, g, or
-t (and an optional 'b') in either case. Returns a pure number representing
-the input or the input itself if unparsable. Discards commas as noise.
-
-=cut
-
-sub parse_bytecount {
- my $bc = shift;
- return $bc if (($bc =~ tr/.//) > 1);
- $bc =~ /^\s*([,\d.]*)\s*([kKmMgGtT]?)[bB]?\s*$/ or return $bc;
- my $base = $1;
- $base =~ tr/,//d;
- return $bc unless length $base;
- my $exponent = index ' kmgt', lc($2);
- return $bc if ($exponent < 0 && $2);
- $exponent = 0 if ($exponent < 0);
- return int($base * 1024 ** $exponent); #bytecounts are integer values
-}
-
-=item display_bytecount AMOUNT
-
-Converts a pure number to a value followed possibly followed by k, m, g, or
-t via Number::Format
-
-=cut
-
-sub display_bytecount {
- my $bc = shift;
- return $bc unless ($bc =~ /^(\d+)$/);
- my $conf = new FS::Conf;
- my $f = new Number::Format;
- my $precision = ( $conf->exists('datavolume-significantdigits') &&
- $conf->config('datavolume-significantdigits') =~ /^\s*\d+\s*$/ )
- ? $conf->config('datavolume-significantdigits')
- : 3;
- my $unit = $conf->exists('datavolume-forcemegabytes') ? 'M' : 'A';
-
- return $f->format_bytes($bc, precision => $precision, unit => $unit);
-}
-
-=back
-
-=head1 BUGS
-
-Fly
-
-=head1 SEE ALSO
-
-L<Number::Format>
-
-=cut
-
-1;
-
diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm
deleted file mode 100644
index bd2b70d..0000000
--- a/FS/FS/UID.pm
+++ /dev/null
@@ -1,390 +0,0 @@
-package FS::UID;
-
-use strict;
-use vars qw(
- @ISA @EXPORT_OK $DEBUG $me $cgi $dbh $freeside_uid $user
- $conf_dir $secrets $datasrc $db_user $db_pass %callback @callback
- $driver_name $AutoCommit $callback_hack $use_confcompat
-);
-use subs qw(
- getsecrets cgisetotaker
-);
-use Exporter;
-use Carp qw(carp croak cluck confess);
-use DBI;
-use IO::File;
-use FS::CurrentUser;
-
-@ISA = qw(Exporter);
-@EXPORT_OK = qw(checkeuid checkruid cgisuidsetup adminsuidsetup forksuidsetup
- getotaker dbh datasrc getsecrets driver_name myconnect
- use_confcompat);
-
-$DEBUG = 0;
-$me = '[FS::UID]';
-
-$freeside_uid = scalar(getpwnam('freeside'));
-
-$conf_dir = "%%%FREESIDE_CONF%%%";
-
-$AutoCommit = 1; #ours, not DBI
-$use_confcompat = 1;
-$callback_hack = 0;
-
-=head1 NAME
-
-FS::UID - Subroutines for database login and assorted other stuff
-
-=head1 SYNOPSIS
-
- use FS::UID qw(adminsuidsetup cgisuidsetup dbh datasrc getotaker
- checkeuid checkruid);
-
- adminsuidsetup $user;
-
- $cgi = new CGI;
- $dbh = cgisuidsetup($cgi);
-
- $dbh = dbh;
-
- $datasrc = datasrc;
-
- $driver_name = driver_name;
-
-=head1 DESCRIPTION
-
-Provides a hodgepodge of subroutines.
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item adminsuidsetup USER
-
-Sets the user to USER (see config.html from the base documentation).
-Cleans the environment.
-Make sure the script is running as freeside, or setuid freeside.
-Opens a connection to the database.
-Swaps real and effective UIDs.
-Runs any defined callbacks (see below).
-Returns the DBI database handle (usually you don't need this).
-
-=cut
-
-sub adminsuidsetup {
- $dbh->disconnect if $dbh;
- &forksuidsetup(@_);
-}
-
-sub forksuidsetup {
- $user = shift;
- my $olduser = $user;
- warn "$me forksuidsetup starting for $user\n" if $DEBUG;
-
- if ( $FS::CurrentUser::upgrade_hack ) {
- $user = 'fs_bootstrap';
- } else {
- croak "fatal: adminsuidsetup called without arguements" unless $user;
-
- $user =~ /^([\w\-\.]+)$/ or croak "fatal: illegal user $user";
- $user = $1;
- }
-
- $ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
- $ENV{'SHELL'} = '/bin/sh';
- $ENV{'IFS'} = " \t\n";
- $ENV{'CDPATH'} = '';
- $ENV{'ENV'} = '';
- $ENV{'BASH_ENV'} = '';
-
- croak "Not running uid freeside!" unless checkeuid();
-
- warn "$me forksuidsetup connecting to database\n" if $DEBUG;
- if ( $FS::CurrentUser::upgrade_hack && $olduser ) {
- $dbh = &myconnect($olduser);
- } else {
- $dbh = &myconnect();
- }
- warn "$me forksuidsetup connected to database with handle $dbh\n" if $DEBUG;
-
- warn "$me forksuidsetup loading schema\n" if $DEBUG;
- use FS::Schema qw(reload_dbdef dbdef);
- reload_dbdef("$conf_dir/dbdef.$datasrc")
- unless $FS::Schema::setup_hack;
-
- warn "$me forksuidsetup deciding upon config system to use\n" if $DEBUG;
-
- if ( dbdef->table('conf') ) {
-
- my $sth = $dbh->prepare("SELECT COUNT(*) FROM conf") or die $dbh->errstr;
- $sth->execute or die $sth->errstr;
- my $confcount = $sth->fetchrow_arrayref->[0];
-
- if ($confcount) {
- $use_confcompat = 0;
- }else{
- warn "NO CONFIGURATION RECORDS FOUND";
- }
-
- } else {
- warn "NO CONFIGURATION TABLE FOUND";
- }
-
- unless ( $callback_hack ) {
- warn "$me calling callbacks\n" if $DEBUG;
- foreach ( keys %callback ) {
- &{$callback{$_}};
- # breaks multi-database installs # delete $callback{$_}; #run once
- }
-
- &{$_} foreach @callback;
- } else {
- warn "$me skipping callbacks (callback_hack set)\n" if $DEBUG;
- }
-
- warn "$me forksuidsetup loading user\n" if $DEBUG;
- FS::CurrentUser->load_user($user);
-
- $dbh;
-}
-
-sub myconnect {
- DBI->connect( getsecrets(@_), { 'AutoCommit' => 0,
- 'ChopBlanks' => 1,
- 'ShowErrorStatement' => 1,
- }
- )
- or die "DBI->connect error: $DBI::errstr\n";
-}
-
-=item install_callback
-
-A package can install a callback to be run in adminsuidsetup by passing
-a coderef to the FS::UID->install_callback class method. If adminsuidsetup has
-run already, the callback will also be run immediately.
-
- $coderef = sub { warn "Hi, I'm returning your call!" };
- FS::UID->install_callback($coderef);
-
- install_callback FS::UID sub {
- warn "Hi, I'm returning your call!"
- };
-
-=cut
-
-sub install_callback {
- my $class = shift;
- my $callback = shift;
- push @callback, $callback;
- &{$callback} if $dbh;
-}
-
-=item cgisuidsetup CGI_object
-
-Takes a single argument, which is a CGI (see L<CGI>) or Apache (see L<Apache>)
-object (CGI::Base is depriciated). Runs cgisetotaker and then adminsuidsetup.
-
-=cut
-
-sub cgisuidsetup {
- $cgi=shift;
- if ( $cgi->isa('CGI::Base') ) {
- carp "Use of CGI::Base is depriciated";
- } elsif ( $cgi->isa('Apache') ) {
-
- } elsif ( ! $cgi->isa('CGI') ) {
- croak "fatal: unrecognized object $cgi";
- }
- cgisetotaker;
- adminsuidsetup($user);
-}
-
-=item cgi
-
-Returns the CGI (see L<CGI>) object.
-
-=cut
-
-sub cgi {
- carp "warning: \$FS::UID::cgi isa Apache" if $cgi->isa('Apache');
- $cgi;
-}
-
-=item dbh
-
-Returns the DBI database handle.
-
-=cut
-
-sub dbh {
- $dbh;
-}
-
-=item datasrc
-
-Returns the DBI data source.
-
-=cut
-
-sub datasrc {
- $datasrc;
-}
-
-=item driver_name
-
-Returns just the driver name portion of the DBI data source.
-
-=cut
-
-sub driver_name {
- return $driver_name if defined $driver_name;
- $driver_name = ( split(':', $datasrc) )[1];
-}
-
-sub suidsetup {
- croak "suidsetup depriciated";
-}
-
-=item getotaker
-
-Returns the current Freeside user.
-
-=cut
-
-sub getotaker {
- $user;
-}
-
-=item cgisetotaker
-
-Sets and returns the CGI REMOTE_USER. $cgi should be defined as a CGI.pm
-object (see L<CGI>) or an Apache object (see L<Apache>). Support for CGI::Base
-and derived classes is depriciated.
-
-=cut
-
-sub cgisetotaker {
- if ( $cgi && $cgi->isa('CGI::Base') && defined $cgi->var('REMOTE_USER')) {
- carp "Use of CGI::Base is depriciated";
- $user = lc ( $cgi->var('REMOTE_USER') );
- } elsif ( $cgi && $cgi->isa('CGI') && defined $cgi->remote_user ) {
- $user = lc ( $cgi->remote_user );
- } elsif ( $cgi && $cgi->isa('Apache') ) {
- $user = lc ( $cgi->connection->user );
- } else {
- die "fatal: Can't get REMOTE_USER! for cgi $cgi - you need to setup ".
- "Apache user authentication as documented in httemplate/docs/install.html";
- }
- $user;
-}
-
-=item checkeuid
-
-Returns true if effective UID is that of the freeside user.
-
-=cut
-
-sub checkeuid {
- ( $> == $freeside_uid );
-}
-
-=item checkruid
-
-Returns true if the real UID is that of the freeside user.
-
-=cut
-
-sub checkruid {
- ( $< == $freeside_uid );
-}
-
-=item getsecrets [ USER ]
-
-Sets the user to USER, if supplied.
-Sets and returns the DBI datasource, username and password for this user from
-the `/usr/local/etc/freeside/mapsecrets' file.
-
-=cut
-
-sub getsecrets {
- my($setuser) = shift;
- $user = $setuser if $setuser;
-
- if ( -e "$conf_dir/mapsecrets" ) {
- die "No user!" unless $user;
- my($line) = grep /^\s*($user|\*)\s/,
- map { /^(.*)$/; $1 } readline(new IO::File "$conf_dir/mapsecrets");
- confess "User $user not found in mapsecrets!" unless $line;
- $line =~ /^\s*($user|\*)\s+(.*)$/;
- $secrets = $2;
- die "Illegal mapsecrets line for user?!" unless $secrets;
- } else {
- # no mapsecrets file at all, so do the default thing
- $secrets = 'secrets';
- }
-
- ($datasrc, $db_user, $db_pass) =
- map { /^(.*)$/; $1 } readline(new IO::File "$conf_dir/$secrets")
- or die "Can't get secrets: $conf_dir/$secrets: $!\n";
- undef $driver_name;
- ($datasrc, $db_user, $db_pass);
-}
-
-=item use_confcompat
-
-Returns true whenever we should use 1.7 configuration compatibility.
-
-=cut
-
-sub use_confcompat {
- $use_confcompat;
-}
-
-=back
-
-=head1 CALLBACKS
-
-Warning: this interface is (still) likely to change in future releases.
-
-New (experimental) callback interface:
-
-A package can install a callback to be run in adminsuidsetup by passing
-a coderef to the FS::UID->install_callback class method. If adminsuidsetup has
-run already, the callback will also be run immediately.
-
- $coderef = sub { warn "Hi, I'm returning your call!" };
- FS::UID->install_callback($coderef);
-
- install_callback FS::UID sub {
- warn "Hi, I'm returning your call!"
- };
-
-Old (deprecated) callback interface:
-
-A package can install a callback to be run in adminsuidsetup by putting a
-coderef into the hash %FS::UID::callback :
-
- $coderef = sub { warn "Hi, I'm returning your call!" };
- $FS::UID::callback{'Package::Name'} = $coderef;
-
-=head1 BUGS
-
-Too many package-global variables.
-
-Not OO.
-
-No capabilities yet. When mod_perl and Authen::DBI are implemented,
-cgisuidsetup will go away as well.
-
-Goes through contortions to support non-OO syntax with multiple datasrc's.
-
-Callbacks are (still) inelegant.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<CGI>, L<DBI>, config.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
deleted file mode 100644
index cb48230..0000000
--- a/FS/FS/Upgrade.pm
+++ /dev/null
@@ -1,117 +0,0 @@
-package FS::Upgrade;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK );
-use Exporter;
-use Tie::IxHash;
-use FS::UID qw( dbh driver_name );
-use FS::Record;
-
-use FS::svc_domain;
-$FS::svc_domain::whois_hack = 1;
-
-@ISA = qw( Exporter );
-@EXPORT_OK = qw( upgrade );
-
-=head1 NAME
-
-FS::Upgrade - Database upgrade routines
-
-=head1 SYNOPSIS
-
- use FS::Upgrade;
-
-=head1 DESCRIPTION
-
-Currently this module simply provides a place to store common subroutines for
-database upgrades.
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item
-
-=cut
-
-sub upgrade {
- my %opt = @_;
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- $FS::UID::AutoCommit = 0;
-
- my $data = upgrade_data(%opt);
-
- foreach my $table ( keys %$data ) {
-
- my $class = "FS::$table";
- eval "use $class;";
- die $@ if $@;
-
- if ( $class->can('_upgrade_data') ) {
- $class->_upgrade_data(%opt);
- } else {
- warn "WARNING: asked for upgrade of $table,".
- " but FS::$table has no _upgrade_data method\n";
- }
-
-# my @records = @{ $data->{$table} };
-#
-# foreach my $record ( @records ) {
-# my $args = delete($record->{'_upgrade_args'}) || [];
-# my $object = $class->new( $record );
-# my $error = $object->insert( @$args );
-# die "error inserting record into $table: $error\n"
-# if $error;
-# }
-
- }
-
- if ( $oldAutoCommit ) {
- dbh->commit or die dbh->errstr;
- }
-
-}
-
-
-sub upgrade_data {
- my %opt = @_;
-
- tie my %hash, 'Tie::IxHash',
-
- #reason type and reasons
- 'reason_type' => [],
- 'reason' => [],
-
- #customer credits
- 'cust_credit' => [],
-
- #duplicate history records
- 'h_cust_svc' => [],
-
- #populate cust_pay.otaker
- 'cust_pay' => [],
-
- #populate part_pkg_taxclass for starters
- 'part_pkg_taxclass' => [],
-
- ;
-
- \%hash;
-
-}
-
-
-=back
-
-=head1 BUGS
-
-Sure.
-
-=head1 SEE ALSO
-
-=cut
-
-1;
-
diff --git a/FS/FS/XMLRPC.pm b/FS/FS/XMLRPC.pm
deleted file mode 100644
index fb0e5ac..0000000
--- a/FS/FS/XMLRPC.pm
+++ /dev/null
@@ -1,166 +0,0 @@
-package FS::XMLRPC;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Frontier::RPC2;
-
-# Instead of 'use'ing freeside modules on the fly below, just preload them now.
-use FS;
-use FS::CGI;
-use FS::Conf;
-use FS::Record;
-use FS::cust_main;
-
-use Data::Dumper;
-
-@ISA = qw( );
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::XMLRPC - Object methods for handling XMLRPC requests
-
-=head1 SYNOPSIS
-
- use FS::XMLRPC;
-
- $xmlrpc = new FS::XMLRPC;
-
- ($error, $response_xml) = $xmlrpc->serve($request_xml);
-
-=head1 DESCRIPTION
-
-The FS::XMLRPC object is a mechanisim to access read-only data from freeside's subroutines. It does not, at least not at this point, give you the ability to access methods of freeside objects remotely. It can, however, be used to call subroutines such as FS::cust_main::smart_search and FS::Record::qsearch.
-
-See the serve method below for calling syntax.
-
-=head1 METHODS
-
-=over 4
-
-=item new
-
-Provides a FS::XMLRPC object used to handle incoming XMLRPC requests.
-
-=cut
-
-sub new {
-
- my $class = shift;
- my $self = {};
- bless($self, $class);
-
- $self->{_coder} = new Frontier::RPC2;
-
- return $self;
-
-}
-
-=item serve REQUEST_XML_SCALAR
-
-The serve method takes a scalar containg an XMLRPC request for one of freeside's subroutines (not object methods). Parameters passed in the 'methodCall' will be passed as a list to the subroutine untouched. The return value of the called subroutine _must_ be a freeside object reference (eg. qsearchs) or a list of freeside object references (eg. qsearch, smart_search), _and_, the object(s) returned must support the hashref method. This will be checked first by calling UNIVERSAL::can('FS::class::subroutine', 'hashref').
-
-Return value is an XMLRPC methodResponse containing the results of the call. The result of the subroutine call itself will be coded in the methodResponse as an array of structs, regardless of whether there was many or a single object returned. In other words, after you decode the response, you'll always have an array.
-
-=cut
-
-sub serve {
-
- my ($self, $request_xml) = (shift, shift);
- my $response_xml;
-
- my $coder = $self->{_coder};
- my $call = $coder->decode($request_xml);
-
- warn "Got methodCall with method_name='" . $call->{method_name} . "'"
- if $DEBUG;
-
- $response_xml = $coder->encode_response(&_serve($call->{method_name}, $call->{value}));
-
- return ('', $response_xml);
-
-}
-
-sub _serve { #Subroutine, not method
-
- my ($method_name, $params) = (shift, shift);
-
-
- #die 'Called _serve without parameters' unless ref($params) eq 'ARRAY';
- $params = [] unless (ref($params) eq 'ARRAY');
-
- if ($method_name =~ /^(\w+)\.(\w+)/) {
-
- #my ($class, $sub) = split(/\./, $method_name);
- my ($class, $sub) = ($1, $2);
- my $fssub = "FS::${class}::${sub}";
- warn "fssub: ${fssub}" if $DEBUG;
- warn "params: " . Dumper($params) if $DEBUG;
-
- my @result;
-
- if ($class eq 'Conf') { #Special case for FS::Conf because we need an obj.
-
- if ($sub eq 'config') {
- my $conf = new FS::Conf;
- @result = ($conf->config(@$params));
- } else {
- warn "FS::XMLRPC: Can't call undefined subroutine '${fssub}'";
- }
-
- } else {
-
- unless (UNIVERSAL::can("FS::${class}", $sub)) {
- warn "FS::XMLRPC: Can't call undefined subroutine '${fssub}'";
- # Should we encode an error in the response,
- # or just break silently to the remote caller and complain locally?
- return [];
- }
-
- eval {
- no strict 'refs';
- my $fssub = "FS::${class}::${sub}";
- @result = (&$fssub(@$params));
- };
-
- if ($@) {
- warn "FS::XMLRPC: Error while calling '${fssub}': $@";
- return [];
- }
-
- }
-
- warn Dumper(@result) if $DEBUG;
-
- if (grep { UNIVERSAL::can($_, 'hashref') ? 0 : 1 } @result) {
- #warn "FS::XMLRPC: One or more objects returned from '${fssub}' doesn't " .
- # "support the 'hashref' method.";
-
- # If they're not FS::Record decendants, just return the results unmap'd?
- # This is more flexible, but possibly more error-prone.
- return [ @result ];
- } else {
- return [ map { $_->hashref } @result ];
- }
- } elsif ($method_name eq 'version') {
- return [ $FS::VERSION ];
- } # else...
-
- warn "Unhandle XMLRPC request '${method_name}'";
- return [];
-
-}
-
-=head1 BUGS
-
-Probably lots.
-
-=head1 SEE ALSO
-
-L<Frontier::RPC2>.
-
-=cut
-
-1;
-
diff --git a/FS/FS/access_group.pm b/FS/FS/access_group.pm
deleted file mode 100644
index b5b693a..0000000
--- a/FS/FS/access_group.pm
+++ /dev/null
@@ -1,162 +0,0 @@
-package FS::access_group;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::m2name_Common;
-use FS::access_groupagent;
-use FS::access_right;
-
-@ISA = qw(FS::m2m_Common FS::m2name_Common FS::Record);
-
-=head1 NAME
-
-FS::access_group - Object methods for access_group records
-
-=head1 SYNOPSIS
-
- use FS::access_group;
-
- $record = new FS::access_group \%hash;
- $record = new FS::access_group { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::access_group object represents an access group. FS::access_group inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item groupnum - primary key
-
-=item groupname - Access group name
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new access group. To add the access group to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'access_group'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid access group. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('groupnum')
- || $self->ut_text('groupname')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item access_groupagent
-
-Returns all associated FS::access_groupagent records.
-
-=cut
-
-sub access_groupagent {
- my $self = shift;
- qsearch('access_groupagent', { 'groupnum' => $self->groupnum } );
-}
-
-=item access_rights
-
-Returns all associated FS::access_right records.
-
-=cut
-
-sub access_rights {
- my $self = shift;
- qsearch('access_right', { 'righttype' => 'FS::access_group',
- 'rightobjnum' => $self->groupnum
- }
- );
-}
-
-=item access_right RIGHTNAME
-
-Returns the specified FS::access_right record. Can be used as a boolean, to
-test if this group has the given RIGHTNAME.
-
-=cut
-
-sub access_right {
- my( $self, $name ) = @_;
- qsearchs('access_right', { 'righttype' => 'FS::access_group',
- 'rightobjnum' => $self->groupnum,
- 'rightname' => $name,
- }
- );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/access_groupagent.pm b/FS/FS/access_groupagent.pm
deleted file mode 100644
index 3de8fee..0000000
--- a/FS/FS/access_groupagent.pm
+++ /dev/null
@@ -1,134 +0,0 @@
-package FS::access_groupagent;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::agent;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::access_groupagent - Object methods for access_groupagent records
-
-=head1 SYNOPSIS
-
- use FS::access_groupagent;
-
- $record = new FS::access_groupagent \%hash;
- $record = new FS::access_groupagent { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::access_groupagent object represents an group reseller virtualization. FS::access_groupagent inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item groupagentnum - primary key
-
-=item groupnum -
-
-=item agentnum -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new group reseller virtualization. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'access_groupagent'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid group reseller virtualization. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('groupagentnum')
- || $self->ut_foreign_key('groupnum', 'access_group', 'groupnum')
- || $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item agent
-
-Returns the associated FS::agent object.
-
-=cut
-
-sub agent {
- my $self = shift;
- qsearchs('agent', { 'agentnum' => $self->agentnum } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm
deleted file mode 100644
index cf9730d..0000000
--- a/FS/FS/access_right.pm
+++ /dev/null
@@ -1,127 +0,0 @@
-package FS::access_right;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::access_right - Object methods for access_right records
-
-=head1 SYNOPSIS
-
- use FS::access_right;
-
- $record = new FS::access_right \%hash;
- $record = new FS::access_right { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::access_right object represents a granted access right. FS::access_right
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item rightnum - primary key
-
-=item righttype -
-
-=item rightobjnum -
-
-=item rightname -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new right. To add the right to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'access_right'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid right. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('rightnum')
- || $self->ut_text('righttype')
- || $self->ut_text('rightobjnum')
- || $self->ut_text('rightname')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-The author forgot to customize this manpage.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
deleted file mode 100644
index a755daf..0000000
--- a/FS/FS/access_user.pm
+++ /dev/null
@@ -1,433 +0,0 @@
-package FS::access_user;
-
-use strict;
-use vars qw( @ISA $htpasswd_file );
-use FS::UID;
-use FS::Conf;
-use FS::Record qw( qsearch qsearchs dbh );
-use FS::m2m_Common;
-use FS::option_Common;
-use FS::access_usergroup;
-use FS::agent;
-
-@ISA = qw( FS::m2m_Common FS::option_Common FS::Record );
-#@ISA = qw( FS::m2m_Common FS::option_Common );
-
-#kludge htpasswd for now (i hope this bootstraps okay)
-FS::UID->install_callback( sub {
- my $conf = new FS::Conf;
- $htpasswd_file = $conf->base_dir. '/htpasswd';
-} );
-
-=head1 NAME
-
-FS::access_user - Object methods for access_user records
-
-=head1 SYNOPSIS
-
- use FS::access_user;
-
- $record = new FS::access_user \%hash;
- $record = new FS::access_user { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::access_user object represents an internal access user. FS::access_user inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item usernum - primary key
-
-=item username -
-
-=item _password -
-
-=item last -
-
-=item first -
-
-=item disabled - empty or 'Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new internal access user. To add the user to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'access_user'; }
-
-sub _option_table { 'access_user_pref'; }
-sub _option_namecol { 'prefname'; }
-sub _option_valuecol { 'prefvalue'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub insert {
- my $self = shift;
-
- my $error = $self->check;
- return $error if $error;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $error = $self->htpasswd_kludge();
- if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- return $error;
- }
-
- $error = $self->SUPER::insert(@_);
-
- if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
-
- #make sure it isn't a dup username? or you could nuke people's passwords
- #blah. really just should do our own login w/cookies
- #and auth out of the db in the first place
- #my $hterror = $self->htpasswd_kludge('-D');
- #$error .= " - additionally received error cleaning up htpasswd file: $hterror"
- return $error;
-
- } else {
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
- }
-
-}
-
-sub htpasswd_kludge {
- my $self = shift;
-
- #awful kludge to skip setting htpasswd for fs_* users
- return '' if $self->username =~ /^fs_/;
-
- unshift @_, '-c' unless -e $htpasswd_file;
- if (
- system('htpasswd', '-b', @_,
- $htpasswd_file,
- $self->username,
- $self->_password,
- ) == 0
- )
- {
- return '';
- } else {
- return 'htpasswd exited unsucessfully';
- }
-}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error =
- $self->SUPER::delete(@_)
- || $self->htpasswd_kludge('-D')
- ;
-
- if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- return $error;
- } else {
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
- }
-
-}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my $new = shift;
-
- my $old = ( ref($_[0]) eq ref($new) )
- ? shift
- : $new->replace_old;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ( $new->_password ne $old->_password ) {
- my $error = $new->htpasswd_kludge();
- if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- return $error;
- }
- }
-
- my $error = $new->SUPER::replace($old, @_);
-
- if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- return $error;
- } else {
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
- }
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid internal access user. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('usernum')
- || $self->ut_alpha_lower('username')
- || $self->ut_text('_password')
- || $self->ut_text('last')
- || $self->ut_text('first')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item name
-
-Returns a name string for this user: "Last, First".
-
-=cut
-
-sub name {
- my $self = shift;
- $self->get('last'). ', '. $self->first;
-}
-
-=item access_usergroup
-
-=cut
-
-sub access_usergroup {
- my $self = shift;
- qsearch( 'access_usergroup', { 'usernum' => $self->usernum } );
-}
-
-#=item access_groups
-#
-#=cut
-#
-#sub access_groups {
-#
-#}
-#
-#=item access_groupnames
-#
-#=cut
-#
-#sub access_groupnames {
-#
-#}
-
-=item agentnums
-
-Returns a list of agentnums this user can view (via group membership).
-
-=cut
-
-sub agentnums {
- my $self = shift;
- my $sth = dbh->prepare(
- "SELECT DISTINCT agentnum FROM access_usergroup
- JOIN access_groupagent USING ( groupnum )
- WHERE usernum = ?"
- ) or die dbh->errstr;
- $sth->execute($self->usernum) or die $sth->errstr;
- map { $_->[0] } @{ $sth->fetchall_arrayref };
-}
-
-=item agentnums_href
-
-Returns a hashref of agentnums this user can view.
-
-=cut
-
-sub agentnums_href {
- my $self = shift;
- scalar( { map { $_ => 1 } $self->agentnums } );
-}
-
-=item agentnums_sql [ HASHREF | OPTION => VALUE ... ]
-
-Returns an sql fragement to select only agentnums this user can view.
-
-Options are passed as a hashref or a list. Available options are:
-
-=over 4
-
-=item null
-
-The frament will also allow the selection of null agentnums.
-
-=item null_right
-
-The fragment will also allow the selection of null agentnums if the current
-user has the provided access right
-
-=item table
-
-Optional table name in which agentnum is being checked. Sometimes required to
-resolve 'column reference "agentnum" is ambiguous' errors.
-
-=back
-
-=cut
-
-sub agentnums_sql {
- my( $self ) = shift;
- my %opt = ref($_[0]) ? %{$_[0]} : @_;
-
- my $agentnum = $opt{'table'} ? $opt{'table'}.'.agentnum' : 'agentnum';
-
- my @agentnums = map { "$agentnum = $_" } $self->agentnums;
-
- push @agentnums, "$agentnum IS NULL"
- if $opt{'null'}
- || ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) );
-
- return ' 1 = 0 ' unless scalar(@agentnums);
- '( '. join( ' OR ', @agentnums ). ' )';
-}
-
-=item agentnum
-
-Returns true if the user can view the specified agent.
-
-=cut
-
-sub agentnum {
- my( $self, $agentnum ) = @_;
- my $sth = dbh->prepare(
- "SELECT COUNT(*) FROM access_usergroup
- JOIN access_groupagent USING ( groupnum )
- WHERE usernum = ? AND agentnum = ?"
- ) or die dbh->errstr;
- $sth->execute($self->usernum, $agentnum) or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
-
-=item agents
-
-Returns the list of agents this user can view (via group membership), as
-FS::agent objects.
-
-=cut
-
-sub agents {
- my $self = shift;
- qsearch({
- 'table' => 'agent',
- 'hashref' => { disabled=>'' },
- 'extra_sql' => ' AND '. $self->agentnums_sql,
- });
-}
-
-=item access_right
-
-Given a right name, returns true if this user has this right (currently via
-group membership, eventually also via user overrides).
-
-=cut
-
-sub access_right {
- my( $self, $rightname ) = @_;
- my $sth = dbh->prepare("
- SELECT groupnum FROM access_usergroup
- LEFT JOIN access_group USING ( groupnum )
- LEFT JOIN access_right
- ON ( access_group.groupnum = access_right.rightobjnum )
- WHERE usernum = ?
- AND righttype = 'FS::access_group'
- AND rightname = ?
- ") or die dbh->errstr;
- $sth->execute($self->usernum, $rightname) or die $sth->errstr;
- my $row = $sth->fetchrow_arrayref;
- $row ? $row->[0] : '';
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/access_user_pref.pm b/FS/FS/access_user_pref.pm
deleted file mode 100644
index a445d31..0000000
--- a/FS/FS/access_user_pref.pm
+++ /dev/null
@@ -1,129 +0,0 @@
-package FS::access_user_pref;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::access_user_pref - Object methods for access_user_pref records
-
-=head1 SYNOPSIS
-
- use FS::access_user_pref;
-
- $record = new FS::access_user_pref \%hash;
- $record = new FS::access_user_pref { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::access_user_pref object represents an per-user preference. Preferenaces
-are also used to store transient state information (server-side "cookies").
-FS::access_user_pref inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item prefnum - primary key
-
-=item usernum - Internal access user (see L<FS::access_user>)
-
-=item prefname -
-
-=item prefvalue -
-
-=item expiration -
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new preference. To add the preference to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'access_user_pref'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid preference. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('prefnum')
- || $self->ut_number('usernum')
- || $self->ut_text('prefname')
- #|| $self->ut_textn('prefvalue')
- || $self->ut_anything('prefvalue')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::access_user>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/access_usergroup.pm b/FS/FS/access_usergroup.pm
deleted file mode 100644
index 8e83060..0000000
--- a/FS/FS/access_usergroup.pm
+++ /dev/null
@@ -1,145 +0,0 @@
-package FS::access_usergroup;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::access_user;
-use FS::access_group;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::access_usergroup - Object methods for access_usergroup records
-
-=head1 SYNOPSIS
-
- use FS::access_usergroup;
-
- $record = new FS::access_usergroup \%hash;
- $record = new FS::access_usergroup { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::access_usergroup object represents an internal access user's membership
-in a group. FS::access_usergroup inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item usergroupnum - primary key
-
-=item usernum -
-
-=item groupnum -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'access_usergroup'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('usergroupnum')
- || $self->ut_number('usernum')
- || $self->ut_number('groupnum')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item access_user
-
-=cut
-
-sub access_user {
- my $self = shift;
- qsearchs( 'access_user', { 'usernum' => $self->usernum } );
-}
-
-=item access_group
-
-=cut
-
-sub access_group {
- my $self = shift;
- qsearchs( 'access_group', { 'groupnum' => $self->groupnum } );
-}
-
-=back
-
-=head1 BUGS
-
-The author forgot to customize this manpage.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/acct_rt_transaction.pm b/FS/FS/acct_rt_transaction.pm
deleted file mode 100644
index ef0a275..0000000
--- a/FS/FS/acct_rt_transaction.pm
+++ /dev/null
@@ -1,316 +0,0 @@
-package FS::acct_rt_transaction;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs dbh );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::acct_rt_transaction - Object methods for acct_rt_transaction records
-
-=head1 SYNOPSIS
-
- use FS::acct_rt_transaction;
-
- $record = new FS::acct_rt_transaction \%hash;
- $record = new FS::acct_rt_transaction { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::acct_rt_transaction object represents an application of time
-from a rt transaction to a svc_acct. FS::acct_rt_transaction inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item svcrtid
-
-Primary key
-
-=item svcnum
-
-The svcnum of the svc_acct to which the time applies
-
-=item transaction_id
-
-The id of the rt transtaction from which the time applies
-
-=item seconds
-
-The amount of time applied from tickets
-
-=item support
-
-The amount of time applied to support services
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new acct_rt_transaction. To add the example to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'acct_rt_transaction'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub insert {
- my( $self, %options ) = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::insert($options{options} ? %{$options{options}} : ());
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- my $svc_acct = qsearchs('svc_acct', {'svcnum' => $self->svcnum});
- unless ($svc_acct) {
- $dbh->rollback if $oldAutoCommit;
- return "Can't find svc_acct " . $self->svcnum;
- }
-
- $error = $svc_acct->decrement_seconds($self->support);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error incrementing service seconds: $error";
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- my $svc_acct = qsearchs('svc_acct', {'svcnum' => $self->svcnum});
- unless ($svc_acct) {
- $dbh->rollback if $oldAutoCommit;
- return "Can't find svc_acct " . $self->svcnum;
- }
-
- $error = $svc_acct->increment_seconds($self->support);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error incrementing service seconds: $error";
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-=item check
-
-Checks all fields to make sure this is a valid acct_rt_transaction. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my ($selfref) = $self->hashref;
-
- my $error =
- $self->ut_numbern('svcrtid')
- || $self->ut_numbern('svcnum')
- || $self->ut_number('transaction_id')
- || $self->ut_numbern('_date')
- || $self->ut_snumber('seconds')
- || $self->ut_snumber('support')
- ;
- return $error if $error;
-
- $self->_date(time) unless $self->_date;
-
- if ($selfref->{custnum}) {
- my $conf = new FS::Conf;
- my %packages = map { $_ => 1 } $conf->config('support_packages');
- my $cust_main = qsearchs('cust_main',{ 'custnum' => $selfref->{custnum} } );
- return "Invalid custnum: " . $selfref->{custnum} unless $cust_main;
-
- my (@svcs) = map { $_->svcnum } $cust_main->support_services;
- return "svcnum ". $self->svcnum. " invalid for custnum ".$selfref->{custnum}
- unless (!$self->svcnum || scalar(grep { $_ == $self->svcnum } @svcs));
-
- $self->svcnum($svcs[0]) unless $self->svcnum;
- return "Can't find support service for custnum ".$selfref->{custnum}
- unless $self->svcnum;
- }
-
- $self->SUPER::check;
-}
-
-=item creator
-
-Returns the creator of the RT transaction associated with this object.
-
-=cut
-
-sub creator {
- my $self = shift;
- FS::TicketSystem->transaction_creator($self->transaction_id);
-}
-
-=item ticketid
-
-Returns the number of the RT ticket associated with this object.
-
-=cut
-
-sub ticketid {
- my $self = shift;
- FS::TicketSystem->transaction_ticketid($self->transaction_id);
-}
-
-=item subject
-
-Returns the subject of the RT ticket associated with this object.
-
-=cut
-
-sub subject {
- my $self = shift;
- FS::TicketSystem->transaction_subject($self->transaction_id);
-}
-
-=item status
-
-Returns the status of the RT ticket associated with this object.
-
-=cut
-
-sub status {
- my $self = shift;
- FS::TicketSystem->transaction_status($self->transaction_id);
-}
-
-=item batch_insert SVC_ACCT_RT_TRANSACTION_OBJECT, ...
-
-Class method which inserts multiple time applications. Takes a list of
-FS::acct_rt_transaction objects. If there is an error inserting any
-application, the entire transaction is rolled back, i.e. all time is applied
-or none is.
-
-For example:
-
- my $errors = FS::acct_rt_transaction->batch_insert(@transactions);
- if ( $error ) {
- #success; all payments were inserted
- } else {
- #failure; no payments were inserted.
- }
-
-=cut
-
-sub batch_insert {
- my $self = shift; #class method
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error;
- foreach (@_) {
- $error = $_->insert;
- last if $error;
- }
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- } else {
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- }
-
- $error;
-
-}
-
-=back
-
-=head1 BUGS
-
-Possibly the delete method or others.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/acct_snarf.pm b/FS/FS/acct_snarf.pm
deleted file mode 100644
index b4e88bf..0000000
--- a/FS/FS/acct_snarf.pm
+++ /dev/null
@@ -1,128 +0,0 @@
-package FS::acct_snarf;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::acct_snarf - Object methods for acct_snarf records
-
-=head1 SYNOPSIS
-
- use FS::acct_snarf;
-
- $record = new FS::acct_snarf \%hash;
- $record = new FS::acct_snarf { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::svc_acct object represents an external mail account, typically for
-download of mail. FS::acct_snarf inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item snarfnum - primary key
-
-=item svcnum - Account (see L<FS::svc_acct>)
-
-=item machine - external machine to download mail from
-
-=item protocol - protocol (pop3, imap, etc.)
-
-=item username - external login username
-
-=item _password - external login password
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'acct_snarf'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid external mail account. If
-there is an error, returns the error, otherwise returns false. Called by the
-insert and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
- my $error =
- $self->ut_numbern('snarfnum')
- || $self->ut_number('svcnum')
- || $self->ut_foreign_key('svcnum', 'svc_acct', 'svcnum')
- || $self->ut_domain('machine')
- || $self->ut_alphan('protocol')
- || $self->ut_textn('username')
- ;
- return $error if $error;
-
- $self->_password =~ /^[^\t\n]*$/ or return "illegal password";
- $self->_password($1);
-
- ''; #no error
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
deleted file mode 100755
index 208684b..0000000
--- a/FS/FS/addr_block.pm
+++ /dev/null
@@ -1,341 +0,0 @@
-package FS::addr_block;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearchs qsearch dbh );
-use FS::router;
-use FS::svc_broadband;
-use FS::Conf;
-use NetAddr::IP;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::addr_block - Object methods for addr_block records
-
-=head1 SYNOPSIS
-
- use FS::addr_block;
-
- $record = new FS::addr_block \%hash;
- $record = new FS::addr_block { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::addr_block record describes an address block assigned for broadband
-access. FS::addr_block inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item blocknum - primary key, used in FS::svc_broadband to associate
-services to the block.
-
-=item routernum - the router (see FS::router) to which this
-block is assigned.
-
-=item ip_gateway - the gateway address used by customers within this block.
-
-=item ip_netmask - the netmask of the block, expressed as an integer.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Create a new record. To add the record to the database, see "insert".
-
-=cut
-
-sub table { 'addr_block'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this record from the database. If there is an error, returns the
-error, otherwise returns false.
-
-sub delete {
- my $self = shift;
- return 'Block must be deallocated before deletion'
- if $self->router;
-
- $self->SUPER::delete;
-}
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_number('routernum')
- || $self->ut_ip('ip_gateway')
- || $self->ut_number('ip_netmask')
- ;
- return $error if $error;
-
-
- # A routernum of 0 indicates an unassigned block and is allowed
- return "Unknown routernum"
- if ($self->routernum and not $self->router);
-
- my $self_addr = $self->NetAddr;
- return "Cannot parse address: ". $self->ip_gateway . '/' . $self->ip_netmask
- unless $self_addr;
-
- if (not $self->blocknum) {
- my @block = grep {
- my $block_addr = $_->NetAddr;
- if($block_addr->contains($self_addr)
- or $self_addr->contains($block_addr)) { $_; };
- } qsearch( 'addr_block', {});
- foreach(@block) {
- return "Block intersects existing block ".$_->ip_gateway."/".$_->ip_netmask;
- }
- }
-
- $self->SUPER::check;
-}
-
-
-=item router
-
-Returns the FS::router object corresponding to this object. If the
-block is unassigned, returns undef.
-
-=cut
-
-sub router {
- my $self = shift;
- return qsearchs('router', { routernum => $self->routernum });
-}
-
-=item svc_broadband
-
-Returns a list of FS::svc_broadband objects associated
-with this object.
-
-=cut
-
-sub svc_broadband {
- my $self = shift;
- return qsearch('svc_broadband', { blocknum => $self->blocknum });
-}
-
-=item NetAddr
-
-Returns a NetAddr::IP object for this block's address and netmask.
-
-=cut
-
-sub NetAddr {
- my $self = shift;
- new NetAddr::IP ($self->ip_gateway, $self->ip_netmask);
-}
-
-=item cidr
-
-Returns a CIDR string for this block's address and netmask, i.e. 10.4.20.0/24
-
-=cut
-
-sub cidr {
- my $self = shift;
- $self->NetAddr->cidr;
-}
-
-=item next_free_addr
-
-Returns a NetAddr::IP object corresponding to the first unassigned address
-in the block (other than the network, broadcast, or gateway address). If
-there are no free addresses, returns false.
-
-=cut
-
-sub next_free_addr {
- my $self = shift;
-
- my $conf = new FS::Conf;
- my @excludeaddr = $conf->config('exclude_ip_addr');
-
-my @used =
-( (map { $_->NetAddr->addr }
- ($self,
- qsearch('svc_broadband', { blocknum => $self->blocknum }))
- ), @excludeaddr
-);
-
- my @free = $self->NetAddr->hostenum;
- while (my $ip = shift @free) {
- if (not grep {$_ eq $ip->addr;} @used) { return $ip; };
- }
-
- '';
-
-}
-
-=item allocate
-
-Allocates this address block to a router. Takes an FS::router object
-as an argument.
-
-At present it's not possible to reallocate a block to a different router
-except by deallocating it first, which requires that none of its addresses
-be assigned. This is probably as it should be.
-
-=cut
-
-sub allocate {
- my ($self, $router) = @_;
-
- return 'Block is already allocated'
- if($self->router);
-
- return 'Block must be allocated to a router'
- unless(ref $router eq 'FS::router');
-
- my @svc = $self->svc_broadband;
- if (@svc) {
- return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc;
- }
-
- my $new = new FS::addr_block {$self->hash};
- $new->routernum($router->routernum);
- return $new->replace($self);
-
-}
-
-=item deallocate
-
-Deallocates the block (i.e. sets the routernum to 0). If any addresses in the
-block are assigned to services, it fails.
-
-=cut
-
-sub deallocate {
- my $self = shift;
-
- my @svc = $self->svc_broadband;
- if (@svc) {
- return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc;
- }
-
- my $new = new FS::addr_block {$self->hash};
- $new->routernum(0);
- return $new->replace($self);
-}
-
-=item split_block
-
-Splits this address block into two equal blocks, occupying the same space as
-the original block. The first of the two will also have the same blocknum.
-The gateway address of each block will be set to the first usable address, i.e.
-(network address)+1. Since this method is designed for use on unallocated
-blocks, this is probably the correct behavior.
-
-(At present, splitting allocated blocks is disallowed. Anyone who wants to
-implement this is reminded that each split costs three addresses, and any
-customers who were using these addresses will have to be moved; depending on
-how full the block was before being split, they might have to be moved to a
-different block. Anyone who I<still> wants to implement it is asked to tie it
-to a configuration switch so that site admins can disallow it.)
-
-=cut
-
-sub split_block {
-
- # We should consider using Attribute::Handlers/Aspect/Hook::LexWrap/
- # something to atomicize functions, so that we can say
- #
- # sub split_block : atomic {
- #
- # instead of repeating all this AutoCommit verbage in every
- # sub that does more than one database operation.
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $self = shift;
- my $error;
-
- if ($self->router) {
- return 'Block is already allocated';
- }
-
- #TODO: Smallest allowed block should be a config option.
- if ($self->NetAddr->masklen() ge 30) {
- return 'Cannot split blocks with a mask length >= 30';
- }
-
- my (@new, @ip);
- $ip[0] = $self->NetAddr;
- @ip = map {$_->first()} $ip[0]->split($self->ip_netmask + 1);
-
- foreach (0,1) {
- $new[$_] = new FS::addr_block {$self->hash};
- $new[$_]->ip_gateway($ip[$_]->addr);
- $new[$_]->ip_netmask($ip[$_]->masklen);
- }
-
- $new[1]->blocknum('');
-
- $error = $new[0]->replace($self);
- if ($error) {
- $dbh->rollback;
- return $error;
- }
-
- $error = $new[1]->insert;
- if ($error) {
- $dbh->rollback;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- return '';
-}
-
-=item merge
-
-To be implemented.
-
-=back
-
-=head1 BUGS
-
-Minimum block size should be a config option. It's hardcoded at /30 right
-now because that's the smallest block that makes any sense at all.
-
-=cut
-
-1;
-
diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm
deleted file mode 100644
index 57cc945..0000000
--- a/FS/FS/agent.pm
+++ /dev/null
@@ -1,445 +0,0 @@
-package FS::agent;
-
-use strict;
-use vars qw( @ISA );
-#use Crypt::YAPassGen;
-use FS::Record qw( dbh qsearch qsearchs );
-use FS::cust_main;
-use FS::cust_pkg;
-use FS::agent_type;
-use FS::reg_code;
-use FS::TicketSystem;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::agent - Object methods for agent records
-
-=head1 SYNOPSIS
-
- use FS::agent;
-
- $record = new FS::agent \%hash;
- $record = new FS::agent { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $agent_type = $record->agent_type;
-
- $hashref = $record->pkgpart_hashref;
- #may purchase $pkgpart if $hashref->{$pkgpart};
-
-=head1 DESCRIPTION
-
-An FS::agent object represents an agent. Every customer has an agent. Agents
-can be used to track things like resellers or salespeople. FS::agent inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item agentnum - primary key (assigned automatically for new agents)
-
-=item agent - Text name of this agent
-
-=item typenum - Agent type. See L<FS::agent_type>
-
-=item prog - For future use.
-
-=item freq - For future use.
-
-=item disabled - Disabled flag, empty or 'Y'
-
-=item username - Username for the Agent interface
-
-=item _password - Password for the Agent interface
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new agent. To add the agent to the database, see L<"insert">.
-
-=cut
-
-sub table { 'agent'; }
-
-=item insert
-
-Adds this agent to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this agent from the database. Only agents with no customers can be
-deleted. If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- return "Can't delete an agent with customers!"
- if qsearch( 'cust_main', { 'agentnum' => $self->agentnum } );
-
- $self->SUPER::delete;
-}
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid agent. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('agentnum')
- || $self->ut_text('agent')
- || $self->ut_number('typenum')
- || $self->ut_numbern('freq')
- || $self->ut_textn('prog')
- || $self->ut_textn('invoice_template')
- ;
- return $error if $error;
-
- if ( $self->dbdef_table->column('disabled') ) {
- $error = $self->ut_enum('disabled', [ '', 'Y' ] );
- return $error if $error;
- }
-
- if ( $self->dbdef_table->column('username') ) {
- $error = $self->ut_alphan('username');
- return $error if $error;
- if ( length($self->username) ) {
- my $conflict = qsearchs('agent', { 'username' => $self->username } );
- return 'duplicate agent username (with '. $conflict->agent. ')'
- if $conflict && $conflict->agentnum != $self->agentnum;
- $error = $self->ut_text('password'); # ut_text... arbitrary choice
- } else {
- $self->_password('');
- }
- }
-
- return "Unknown typenum!"
- unless $self->agent_type;
-
- $self->SUPER::check;
-}
-
-=item agent_type
-
-Returns the FS::agent_type object (see L<FS::agent_type>) for this agent.
-
-=cut
-
-sub agent_type {
- my $self = shift;
- qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
-}
-
-=item pkgpart_hashref
-
-Returns a hash reference. The keys of the hash are pkgparts. The value is
-true if this agent may purchase the specified package definition. See
-L<FS::part_pkg>.
-
-=cut
-
-sub pkgpart_hashref {
- my $self = shift;
- $self->agent_type->pkgpart_hashref;
-}
-
-=item ticketing_queue
-
-Returns the queue name corresponding with the id from the I<ticketing_queueid>
-field, or the empty string.
-
-=cut
-
-sub ticketing_queue {
- my $self = shift;
- FS::TicketSystem->queue($self->ticketing_queueid);
-};
-
-=item num_prospect_cust_main
-
-Returns the number of prospects (customers with no packages ever ordered) for
-this agent.
-
-=cut
-
-sub num_prospect_cust_main {
- shift->num_sql(FS::cust_main->prospect_sql);
-}
-
-sub num_sql {
- my( $self, $sql ) = @_;
- my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
- my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
- $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
- $sth->fetchrow_arrayref->[0];
-}
-
-=item prospect_cust_main
-
-Returns the prospects (customers with no packages ever ordered) for this agent,
-as cust_main objects.
-
-=cut
-
-sub prospect_cust_main {
- shift->cust_main_sql(FS::cust_main->prospect_sql);
-}
-
-sub cust_main_sql {
- my( $self, $sql ) = @_;
- qsearch( 'cust_main',
- { 'agentnum' => $self->agentnum },
- '',
- " AND $sql"
- );
-}
-
-=item num_active_cust_main
-
-Returns the number of active customers for this agent (customers with active
-recurring packages).
-
-=cut
-
-sub num_active_cust_main {
- shift->num_sql(FS::cust_main->active_sql);
-}
-
-=item active_cust_main
-
-Returns the active customers for this agent, as cust_main objects.
-
-=cut
-
-sub active_cust_main {
- shift->cust_main_sql(FS::cust_main->active_sql);
-}
-
-=item num_inactive_cust_main
-
-Returns the number of inactive customers for this agent (customers with no
-active recurring packages, but otherwise unsuspended/uncancelled).
-
-=cut
-
-sub num_inactive_cust_main {
- shift->num_sql(FS::cust_main->inactive_sql);
-}
-
-=item inactive_cust_main
-
-Returns the inactive customers for this agent, as cust_main objects.
-
-=cut
-
-sub inactive_cust_main {
- shift->cust_main_sql(FS::cust_main->inactive_sql);
-}
-
-
-=item num_susp_cust_main
-
-Returns the number of suspended customers for this agent.
-
-=cut
-
-sub num_susp_cust_main {
- shift->num_sql(FS::cust_main->susp_sql);
-}
-
-=item susp_cust_main
-
-Returns the suspended customers for this agent, as cust_main objects.
-
-=cut
-
-sub susp_cust_main {
- shift->cust_main_sql(FS::cust_main->susp_sql);
-}
-
-=item num_cancel_cust_main
-
-Returns the number of cancelled customer for this agent.
-
-=cut
-
-sub num_cancel_cust_main {
- shift->num_sql(FS::cust_main->cancel_sql);
-}
-
-=item cancel_cust_main
-
-Returns the cancelled customers for this agent, as cust_main objects.
-
-=cut
-
-sub cancel_cust_main {
- shift->cust_main_sql(FS::cust_main->cancel_sql);
-}
-
-=item num_active_cust_pkg
-
-Returns the number of active customer packages for this agent.
-
-=cut
-
-sub num_active_cust_pkg {
- shift->num_pkg_sql(FS::cust_pkg->active_sql);
-}
-
-sub num_pkg_sql {
- my( $self, $sql ) = @_;
- my $statement =
- "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
- " WHERE agentnum = ? AND $sql";
- my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
- $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
- $sth->fetchrow_arrayref->[0];
-}
-
-=item num_inactive_cust_pkg
-
-Returns the number of inactive customer packages (one-time packages otherwise
-unsuspended/uncancelled) for this agent.
-
-=cut
-
-sub num_inactive_cust_pkg {
- shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
-}
-
-=item num_susp_cust_pkg
-
-Returns the number of suspended customer packages for this agent.
-
-=cut
-
-sub num_susp_cust_pkg {
- shift->num_pkg_sql(FS::cust_pkg->susp_sql);
-}
-
-=item num_cancel_cust_pkg
-
-Returns the number of cancelled customer packages for this agent.
-
-=cut
-
-sub num_cancel_cust_pkg {
- shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
-}
-
-=item generate_reg_codes NUM PKGPART_ARRAYREF
-
-Generates the specified number of registration codes, allowing purchase of the
-specified package definitions. Returns an array reference of the newly
-generated codes, or a scalar error message.
-
-=cut
-
-#false laziness w/prepay_credit::generate
-sub generate_reg_codes {
- my( $self, $num, $pkgparts ) = @_;
-
- my @codeset = ( 'A'..'Z' );
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my @codes = ();
- for ( 1 ... $num ) {
- my $reg_code = new FS::reg_code {
- 'agentnum' => $self->agentnum,
- 'code' => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
- };
- my $error = $reg_code->insert($pkgparts);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- push @codes, $reg_code->code;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- \@codes;
-
-}
-
-=item num_reg_code
-
-Returns the number of unused registration codes for this agent.
-
-=cut
-
-sub num_reg_code {
- my $self = shift;
- my $sth = dbh->prepare(
- "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
- ) or die dbh->errstr;
- $sth->execute($self->agentnum) or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
-
-=item num_prepay_credit
-
-Returns the number of unused prepaid cards for this agent.
-
-=cut
-
-sub num_prepay_credit {
- my $self = shift;
- my $sth = dbh->prepare(
- "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
- ) or die dbh->errstr;
- $sth->execute($self->agentnum) or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
-
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/agent_payment_gateway.pm b/FS/FS/agent_payment_gateway.pm
deleted file mode 100644
index bd99d0c..0000000
--- a/FS/FS/agent_payment_gateway.pm
+++ /dev/null
@@ -1,139 +0,0 @@
-package FS::agent_payment_gateway;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::payment_gateway;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::agent_payment_gateway - Object methods for agent_payment_gateway records
-
-=head1 SYNOPSIS
-
- use FS::agent_payment_gateway;
-
- $record = new FS::agent_payment_gateway \%hash;
- $record = new FS::agent_payment_gateway { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::agent_payment_gateway object represents a payment gateway override for
-a specific agent. FS::agent_payment_gateway inherits from FS::Record. The
-following fields are currently supported:
-
-=over 4
-
-=item agentgatewaynum - primary key
-
-=item agentnum -
-
-=item gatewaynum -
-
-=item cardtype -
-
-=item taxclass -
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new override. To add the override to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'agent_payment_gateway'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid override. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('agentgatewaynum')
- || $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
- || $self->ut_foreign_key('gatewaynum', 'payment_gateway', 'gatewaynum' )
- || $self->ut_textn('cardtype')
- || $self->ut_textn('taxclass')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item payment_gateway
-
-=cut
-
-sub payment_gateway {
- my $self = shift;
- qsearchs('payment_gateway', { 'gatewaynum' => $self->gatewaynum } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::payment_gateway>, L<FS::agent>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/agent_type.pm b/FS/FS/agent_type.pm
deleted file mode 100644
index 2660bb4..0000000
--- a/FS/FS/agent_type.pm
+++ /dev/null
@@ -1,191 +0,0 @@
-package FS::agent_type;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch );
-use FS::m2m_Common;
-use FS::agent;
-use FS::type_pkgs;
-
-@ISA = qw( FS::m2m_Common FS::Record );
-
-=head1 NAME
-
-FS::agent_type - Object methods for agent_type records
-
-=head1 SYNOPSIS
-
- use FS::agent_type;
-
- $record = new FS::agent_type \%hash;
- $record = new FS::agent_type { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $hashref = $record->pkgpart_hashref;
- #may purchase $pkgpart if $hashref->{$pkgpart};
-
- @type_pkgs = $record->type_pkgs;
-
- @pkgparts = $record->pkgpart;
-
-=head1 DESCRIPTION
-
-An FS::agent_type object represents an agent type. Every agent (see
-L<FS::agent>) has an agent type. Agent types define which packages (see
-L<FS::part_pkg>) may be purchased by customers (see L<FS::cust_main>), via
-FS::type_pkgs records (see L<FS::type_pkgs>). FS::agent_type inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item typenum - primary key (assigned automatically for new agent types)
-
-=item atype - Text name of this agent type
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new agent type. To add the agent type to the database, see
-L<"insert">.
-
-=cut
-
-sub table { 'agent_type'; }
-
-=item insert
-
-Adds this agent type to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this agent type from the database. Only agent types with no agents
-can be deleted. If there is an error, returns the error, otherwise returns
-false.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- return "Can't delete an agent_type with agents!"
- if qsearch( 'agent', { 'typenum' => $self->typenum } );
-
- $self->SUPER::delete;
-}
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid agent type. If there is an
-error, returns the error, otherwise returns false. Called by the insert and
-replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('typenum')
- or $self->ut_text('atype')
- or $self->SUPER::check;
-
-}
-
-=item pkgpart_hashref
-
-Returns a hash reference. The keys of the hash are pkgparts. The value is
-true iff this agent may purchase the specified package definition. See
-L<FS::part_pkg>.
-
-=cut
-
-sub pkgpart_hashref {
- my $self = shift;
- my %pkgpart;
- #$pkgpart{$_}++ foreach $self->pkgpart;
- # not compatible w/5.004_04 (fixed in 5.004_05)
- foreach ( $self->pkgpart ) { $pkgpart{$_}++; }
- \%pkgpart;
-}
-
-=item type_pkgs
-
-Returns all FS::type_pkgs objects (see L<FS::type_pkgs>) for this agent type.
-
-=cut
-
-sub type_pkgs {
- my $self = shift;
- qsearch('type_pkgs', { 'typenum' => $self->typenum } );
-}
-
-=item type_pkgs_enabled
-
-Returns all FS::type_pkg objects (see L<FS::type_pkgs>) that link to enabled
-package definitions (see L<FS::part_pkg>).
-
-An additional strange feature is that the returned type_pkg objects also have
-all fields of the associated part_pkg object.
-
-=cut
-
-sub type_pkgs_enabled {
- my $self = shift;
- qsearch({
- 'table' => 'type_pkgs',
- 'addl_from' => 'JOIN part_pkg USING ( pkgpart )',
- 'hashref' => { 'typenum' => $self->typenum },
- 'extra_sql' => " AND ( disabled = '' OR disabled IS NULL )".
- " ORDER BY pkg",
- });
-}
-
-=item pkgpart
-
-Returns the pkgpart of all package definitions (see L<FS::part_pkg>) for this
-agent type.
-
-=cut
-
-sub pkgpart {
- my $self = shift;
- map $_->pkgpart, $self->type_pkgs;
-}
-
-=back
-
-=head1 BUGS
-
-type_pkgs_enabled should order itself by something (pkg?)
-
-type_pkgs_enabled should populate something that caches for the part_pkg method
-rather than add fields to this object, right? In fact we need a "poop" object
-framework that does that automatically for any joined search at some point....
-right?
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::agent>, L<FS::type_pkgs>, L<FS::cust_main>,
-L<FS::part_pkg>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/banned_pay.pm b/FS/FS/banned_pay.pm
deleted file mode 100644
index 1ad87f5..0000000
--- a/FS/FS/banned_pay.pm
+++ /dev/null
@@ -1,136 +0,0 @@
-package FS::banned_pay;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::UID qw( getotaker );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::banned_pay - Object methods for banned_pay records
-
-=head1 SYNOPSIS
-
- use FS::banned_pay;
-
- $record = new FS::banned_pay \%hash;
- $record = new FS::banned_pay { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::banned_pay object represents an banned credit card or ACH account.
-FS::banned_pay inherits from FS::Record. The following fields are currently
-supported:
-
-=over 4
-
-=item bannum - primary key
-
-=item payby - I<CARD> or I<CHEK>
-
-=item payinfo - fingerprint of banned card (base64-encoded MD5 digest)
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item otaker - order taker (assigned automatically, see L<FS::UID>)
-
-=item reason - reason (text)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new ban. To add the ban to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'banned_pay'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid ban. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('bannum')
- || $self->ut_enum('payby', [ 'CARD', 'CHEK' ] )
- || $self->ut_text('payinfo')
- || $self->ut_numbern('_date')
- || $self->ut_textn('reason')
- ;
- return $error if $error;
-
- $self->_date(time) unless $self->_date;
-
- $self->otaker(getotaker);
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm
deleted file mode 100644
index 5078ff6..0000000
--- a/FS/FS/cdr.pm
+++ /dev/null
@@ -1,672 +0,0 @@
-package FS::cdr;
-
-use strict;
-use vars qw( @ISA );
-use Date::Parse;
-use Date::Format;
-use Time::Local;
-use FS::UID qw( dbh );
-use FS::Record qw( qsearch qsearchs );
-use FS::cdr_type;
-use FS::cdr_calltype;
-use FS::cdr_carrier;
-use FS::cdr_upstream_rate;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cdr - Object methods for cdr records
-
-=head1 SYNOPSIS
-
- use FS::cdr;
-
- $record = new FS::cdr \%hash;
- $record = new FS::cdr { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cdr object represents an Call Data Record, typically from a telephony
-system or provider of some sort. FS::cdr inherits from FS::Record. The
-following fields are currently supported:
-
-=over 4
-
-=item acctid - primary key
-
-=item calldate - Call timestamp (SQL timestamp)
-
-=item clid - Caller*ID with text
-
-=item src - Caller*ID number / Source number
-
-=item dst - Destination extension
-
-=item dcontext - Destination context
-
-=item channel - Channel used
-
-=item dstchannel - Destination channel if appropriate
-
-=item lastapp - Last application if appropriate
-
-=item lastdata - Last application data
-
-=item startdate - Start of call (UNIX-style integer timestamp)
-
-=item answerdate - Answer time of call (UNIX-style integer timestamp)
-
-=item enddate - End time of call (UNIX-style integer timestamp)
-
-=item duration - Total time in system, in seconds
-
-=item billsec - Total time call is up, in seconds
-
-=item disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
-
-=item amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode.
-
-=cut
-
- #ignore the "omit" and "documentation" AMAs??
- #AMA = Automated Message Accounting.
- #default: Sets the system default.
- #omit: Do not record calls.
- #billing: Mark the entry for billing
- #documentation: Mark the entry for documentation.
-
-=item accountcode - CDR account number to use: account
-
-=item uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
-
-=item userfield - CDR user-defined field
-
-=item cdr_type - CDR type - see L<FS::cdr_type> (Usage = 1, S&E = 7, OC&C = 8)
-
-=item charged_party - Service number to be billed
-
-=item upstream_currency - Wholesale currency from upstream
-
-=item upstream_price - Wholesale price from upstream
-
-=item upstream_rateplanid - Upstream rate plan ID
-
-=item rated_price - Rated (or re-rated) price
-
-=item distance - km (need units field?)
-
-=item islocal - Local - 1, Non Local = 0
-
-=item calltypenum - Type of call - see L<FS::cdr_calltype>
-
-=item description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc)
-
-=item quantity - Number of items (cdr_type 7&8 only)
-
-=item carrierid - Upstream Carrier ID (see L<FS::cdr_carrier>)
-
-=cut
-
-#Telstra =1, Optus = 2, RSL COM = 3
-
-=item upstream_rateid - Upstream Rate ID
-
-=item svcnum - Link to customer service (see L<FS::cust_svc>)
-
-=item freesidestatus - NULL, done (or something)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new CDR. To add the CDR to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cdr'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid CDR. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-Note: Unlike most types of records, we don't want to "reject" a CDR and we want
-to process them as quickly as possible, so we allow the database to check most
-of the data.
-
-=cut
-
-sub check {
- my $self = shift;
-
-# we don't want to "reject" a CDR like other sorts of input...
-# my $error =
-# $self->ut_numbern('acctid')
-## || $self->ut_('calldate')
-# || $self->ut_text('clid')
-# || $self->ut_text('src')
-# || $self->ut_text('dst')
-# || $self->ut_text('dcontext')
-# || $self->ut_text('channel')
-# || $self->ut_text('dstchannel')
-# || $self->ut_text('lastapp')
-# || $self->ut_text('lastdata')
-# || $self->ut_numbern('startdate')
-# || $self->ut_numbern('answerdate')
-# || $self->ut_numbern('enddate')
-# || $self->ut_number('duration')
-# || $self->ut_number('billsec')
-# || $self->ut_text('disposition')
-# || $self->ut_number('amaflags')
-# || $self->ut_text('accountcode')
-# || $self->ut_text('uniqueid')
-# || $self->ut_text('userfield')
-# || $self->ut_numbern('cdrtypenum')
-# || $self->ut_textn('charged_party')
-## || $self->ut_n('upstream_currency')
-## || $self->ut_n('upstream_price')
-# || $self->ut_numbern('upstream_rateplanid')
-## || $self->ut_n('distance')
-# || $self->ut_numbern('islocal')
-# || $self->ut_numbern('calltypenum')
-# || $self->ut_textn('description')
-# || $self->ut_numbern('quantity')
-# || $self->ut_numbern('carrierid')
-# || $self->ut_numbern('upstream_rateid')
-# || $self->ut_numbern('svcnum')
-# || $self->ut_textn('freesidestatus')
-# ;
-# return $error if $error;
-
- $self->calldate( $self->startdate_sql )
- if !$self->calldate && $self->startdate;
-
- unless ( $self->charged_party ) {
- if ( $self->dst =~ /^(\+?1)?8[02-8]{2}/ ) {
- $self->charged_party($self->dst);
- } else {
- $self->charged_party($self->src);
- }
- }
-
- #check the foreign keys even?
- #do we want to outright *reject* the CDR?
- my $error =
- $self->ut_numbern('acctid')
-
- #Usage = 1, S&E = 7, OC&C = 8
- || $self->ut_foreign_keyn('cdrtypenum', 'cdr_type', 'cdrtypenum' )
-
- #the big list in appendix 2
- || $self->ut_foreign_keyn('calltypenum', 'cdr_calltype', 'calltypenum' )
-
- # Telstra =1, Optus = 2, RSL COM = 3
- || $self->ut_foreign_keyn('carrierid', 'cdr_carrier', 'carrierid' )
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item set_status_and_rated_price STATUS [ RATED_PRICE ]
-
-Sets the status to the provided string. If there is an error, returns the
-error, otherwise returns false.
-
-=cut
-
-sub set_status_and_rated_price {
- my($self, $status, $rated_price) = @_;
- $self->freesidestatus($status);
- $self->rated_price($rated_price);
- $self->replace();
-}
-
-=item calldate_unix
-
-Parses the calldate in SQL string format and returns a UNIX timestamp.
-
-=cut
-
-sub calldate_unix {
- str2time(shift->calldate);
-}
-
-=item startdate_sql
-
-Parses the startdate in UNIX timestamp format and returns a string in SQL
-format.
-
-=cut
-
-sub startdate_sql {
- my($sec,$min,$hour,$mday,$mon,$year) = localtime(shift->startdate);
- $mon++;
- $year += 1900;
- "$year-$mon-$mday $hour:$min:$sec";
-}
-
-=item cdr_carrier
-
-Returns the FS::cdr_carrier object associated with this CDR, or false if no
-carrierid is defined.
-
-=cut
-
-my %carrier_cache = ();
-
-sub cdr_carrier {
- my $self = shift;
- return '' unless $self->carrierid;
- $carrier_cache{$self->carrierid} ||=
- qsearchs('cdr_carrier', { 'carrierid' => $self->carrierid } );
-}
-
-=item carriername
-
-Returns the carrier name (see L<FS::cdr_carrier>), or the empty string if
-no FS::cdr_carrier object is assocated with this CDR.
-
-=cut
-
-sub carriername {
- my $self = shift;
- my $cdr_carrier = $self->cdr_carrier;
- $cdr_carrier ? $cdr_carrier->carriername : '';
-}
-
-=item cdr_calltype
-
-Returns the FS::cdr_calltype object associated with this CDR, or false if no
-calltypenum is defined.
-
-=cut
-
-my %calltype_cache = ();
-
-sub cdr_calltype {
- my $self = shift;
- return '' unless $self->calltypenum;
- $calltype_cache{$self->calltypenum} ||=
- qsearchs('cdr_calltype', { 'calltypenum' => $self->calltypenum } );
-}
-
-=item calltypename
-
-Returns the call type name (see L<FS::cdr_calltype>), or the empty string if
-no FS::cdr_calltype object is assocated with this CDR.
-
-=cut
-
-sub calltypename {
- my $self = shift;
- my $cdr_calltype = $self->cdr_calltype;
- $cdr_calltype ? $cdr_calltype->calltypename : '';
-}
-
-=item cdr_upstream_rate
-
-Returns the upstream rate mapping (see L<FS::cdr_upstream_rate>), or the empty
-string if no FS::cdr_upstream_rate object is associated with this CDR.
-
-=cut
-
-sub cdr_upstream_rate {
- my $self = shift;
- return '' unless $self->upstream_rateid;
- qsearchs('cdr_upstream_rate', { 'upstream_rateid' => $self->upstream_rateid })
- or '';
-}
-
-=item _convergent_format COLUMN [ COUNTRYCODE ]
-
-Returns the number in COLUMN formatted as follows:
-
-If the country code does not match COUNTRYCODE (default "61"), it is returned
-unchanged.
-
-If the country code does match COUNTRYCODE (default "61"), it is removed. In
-addiiton, "0" is prepended unless the number starts with 13, 18 or 19. (???)
-
-=cut
-
-sub _convergent_format {
- my( $self, $field ) = ( shift, shift );
- my $countrycode = scalar(@_) ? shift : '61'; #+61 = australia
- #my $number = $self->$field();
- my $number = $self->get($field);
- #if ( $number =~ s/^(\+|011)$countrycode// ) {
- if ( $number =~ s/^\+$countrycode// ) {
- $number = "0$number"
- unless $number =~ /^1[389]/; #???
- }
- $number;
-}
-
-=item downstream_csv [ OPTION => VALUE, ... ]
-
-=cut
-
-my %export_formats = (
- 'convergent' => [
- 'carriername', #CARRIER
- sub { shift->_convergent_format('src') }, #SERVICE_NUMBER
- sub { shift->_convergent_format('charged_party') }, #CHARGED_NUMBER
- sub { time2str('%Y-%m-%d', shift->calldate_unix ) }, #DATE
- sub { time2str('%T', shift->calldate_unix ) }, #TIME
- 'billsec', #'duration', #DURATION
- sub { shift->_convergent_format('dst') }, #NUMBER_DIALED
- '', #XXX add (from prefixes in most recent email) #FROM_DESC
- '', #XXX add (from prefixes in most recent email) #TO_DESC
- 'calltypename', #CLASS_CODE
- 'rated_price', #PRICE
- sub { shift->rated_price ? 'Y' : 'N' }, #RATED
- '', #OTHER_INFO
- ],
-);
-
-sub downstream_csv {
- my( $self, %opt ) = @_;
-
- my $format = $opt{'format'}; # 'convergent';
- return "Unknown format $format" unless exists $export_formats{$format};
-
- eval "use Text::CSV_XS;";
- die $@ if $@;
- my $csv = new Text::CSV_XS;
-
- my @columns =
- map {
- ref($_) ? &{$_}($self) : $self->$_();
- }
- @{ $export_formats{$format} };
-
- my $status = $csv->combine(@columns);
- die "FS::CDR: error combining ". $csv->error_input(). "into downstream CSV"
- unless $status;
-
- $csv->string;
-
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item batch_import
-
-=cut
-
-my($tmp_mday, $tmp_mon, $tmp_year);
-
-sub _cdr_date_parser_maker {
- my $field = shift;
- return sub {
- my( $cdr, $date ) = @_;
- $cdr->$field( _cdr_date_parse($date) );
- };
-}
-
-sub _cdr_date_parse {
- my $date = shift;
-
- return '' unless length($date); #that's okay, it becomes NULL
-
- #$date =~ /^\s*(\d{4})[\-\/]\(\d{1,2})[\-\/](\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})\s*$/
- $date =~ /^\s*(\d{4})\D(\d{1,2})\D(\d{1,2})\s+(\d{1,2})\D(\d{1,2})\D(\d{1,2})\s*$/
- or die "unparsable date: $date"; #maybe we shouldn't die...
- my($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
-
- timelocal($sec, $min, $hour, $day, $mon-1, $year);
-}
-
-#http://www.the-asterisk-book.com/unstable/funktionen-cdr.html
-my %amaflags = (
- DEFAULT => 0,
- OMIT => 1, #asterisk 1.4+
- IGNORE => 1, #asterisk 1.2
- BILLING => 2, #asterisk 1.4+
- BILL => 2, #asterisk 1.2
- DOCUMENTATION => 3,
- #? '' => 0,
-);
-
-my %import_formats = (
- 'asterisk' => [
- 'accountcode',
- 'src',
- 'dst',
- 'dcontext',
- 'clid',
- 'channel',
- 'dstchannel',
- 'lastapp',
- 'lastdata',
- _cdr_date_parser_maker('startdate'),
- _cdr_date_parser_maker('answerdate'),
- _cdr_date_parser_maker('enddate'),
- 'duration',
- 'billsec',
- 'disposition',
- sub { my($cdr, $amaflags) = @_; $cdr->amaflags($amaflags{$amaflags}); },
- 'uniqueid',
- 'userfield',
- ],
- 'unitel' => [
- 'uniqueid',
- #'cdr_type',
- 'cdrtypenum',
- 'calldate', # may need massaging? huh maybe not...
- #'billsec', #XXX duration and billsec?
- sub { $_[0]->billsec( $_[1] );
- $_[0]->duration( $_[1] );
- },
- 'src',
- 'dst', # XXX needs to have "+61" prepended unless /^\+/ ???
- 'charged_party',
- 'upstream_currency',
- 'upstream_price',
- 'upstream_rateplanid',
- 'distance',
- 'islocal',
- 'calltypenum',
- 'startdate', #XXX needs massaging
- 'enddate', #XXX same
- 'description',
- 'quantity',
- 'carrierid',
- 'upstream_rateid',
- ],
- 'simple' => [
-
- # Date
- sub { my($cdr, $date) = @_;
- $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/
- or die "unparsable date: $date"; #maybe we shouldn't die...
- #$cdr->startdate( timelocal(0, 0, 0 ,$2, $1-1, $3) );
- ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 );
- },
-
- # Time
- sub { my($cdr, $time) = @_;
- #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate);
- $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/
- or die "unparsable time: $time"; #maybe we shouldn't die...
- #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) );
- $cdr->startdate(
- timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year)
- );
- },
-
- # Source_Number
- 'src',
-
- # Terminating_Number
- 'dst',
-
- # Duration
- sub { my($cdr, $min) = @_;
- my $sec = sprintf('%.0f', $min * 60 );
- $cdr->billsec( $sec );
- $cdr->duration( $sec );
- },
-
- ],
-);
-
-sub batch_import {
- my $param = shift;
-
- my $fh = $param->{filehandle};
- my $format = $param->{format};
-
- return "Unknown format $format" unless exists $import_formats{$format};
-
- eval "use Text::CSV_XS;";
- die $@ if $@;
-
- my $csv = new Text::CSV_XS;
-
- my $imported = 0;
- #my $columns;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ( $format eq 'simple' ) { # and other formats with a header too?
-
- }
-
- my $body = 0;
- my $line;
- while ( defined($line=<$fh>) ) {
-
- #skip header...
- if ( ! $body++ && $format eq 'simple' && $line =~ /^[\w\, ]+$/ ) {
- next;
- }
-
- $csv->parse($line) or do {
- $dbh->rollback if $oldAutoCommit;
- return "can't parse: ". $csv->error_input();
- };
-
- my @columns = $csv->fields();
- #warn join('-',@columns);
-
- if ( $format eq 'simple' ) {
- @columns = map { s/^ +//; $_; } @columns;
- }
-
- my @later = ();
- my %cdr =
- map {
-
- my $field_or_sub = $_;
- if ( ref($field_or_sub) ) {
- push @later, $field_or_sub, shift(@columns);
- ();
- } else {
- ( $field_or_sub => shift @columns );
- }
-
- }
- @{ $import_formats{$format} }
- ;
-
- my $cdr = new FS::cdr ( \%cdr );
-
- while ( scalar(@later) ) {
- my $sub = shift @later;
- my $data = shift @later;
- &{$sub}($cdr, $data); # $cdr->&{$sub}($data);
- }
-
- my $error = $cdr->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
-
- #or just skip?
- #next;
- }
-
- $imported++;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- #might want to disable this if we skip records for any reason...
- return "Empty file!" unless $imported;
-
- '';
-
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cdr_calltype.pm b/FS/FS/cdr_calltype.pm
deleted file mode 100644
index fe45608..0000000
--- a/FS/FS/cdr_calltype.pm
+++ /dev/null
@@ -1,115 +0,0 @@
-package FS::cdr_calltype;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cdr_calltype - Object methods for cdr_calltype records
-
-=head1 SYNOPSIS
-
- use FS::cdr_calltype;
-
- $record = new FS::cdr_calltype \%hash;
- $record = new FS::cdr_calltype { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cdr_calltype object represents an CDR call type. FS::cdr_calltype
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item calltypenum - primary key
-
-=item calltypename - CDR call type name
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new call type. To add the call type to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cdr_calltype'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid call type. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('calltypenum')
- || $self->ut_text('calltypename')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cdr_carrier.pm b/FS/FS/cdr_carrier.pm
deleted file mode 100644
index 609c939..0000000
--- a/FS/FS/cdr_carrier.pm
+++ /dev/null
@@ -1,116 +0,0 @@
-package FS::cdr_carrier;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cdr_carrier - Object methods for cdr_carrier records
-
-=head1 SYNOPSIS
-
- use FS::cdr_carrier;
-
- $record = new FS::cdr_carrier \%hash;
- $record = new FS::cdr_carrier { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cdr_carrier object represents an CDR carrier or upstream.
-FS::cdr_carrier inherits from FS::Record. The following fields are currently
-supported:
-
-=over 4
-
-=item carrierid - primary key
-
-=item carriername - Carrier name
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new carrier. To add the carrier to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cdr_carrier'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid carrier. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('carrierid')
- || $self->ut_text('carriername')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cdr_type.pm b/FS/FS/cdr_type.pm
deleted file mode 100644
index e258bf8..0000000
--- a/FS/FS/cdr_type.pm
+++ /dev/null
@@ -1,119 +0,0 @@
-package FS::cdr_type;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cdr_type - Object methods for cdr_type records
-
-=head1 SYNOPSIS
-
- use FS::cdr_type;
-
- $record = new FS::cdr_type \%hash;
- $record = new FS::cdr_type { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cdr_type object represents an CDR type. FS::cdr_type inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item cdrtypenum - primary key
-
-=item typename - CDR type name
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new CDR type. To add the CDR type to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cdr_type'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid CDR type. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('cdrtypenum')
- || $self->ut_text('typename')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cdr_upstream_rate.pm b/FS/FS/cdr_upstream_rate.pm
deleted file mode 100644
index 2fd9782..0000000
--- a/FS/FS/cdr_upstream_rate.pm
+++ /dev/null
@@ -1,138 +0,0 @@
-package FS::cdr_upstream_rate;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::rate_detail;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cdr_upstream_rate - Object methods for cdr_upstream_rate records
-
-=head1 SYNOPSIS
-
- use FS::cdr_upstream_rate;
-
- $record = new FS::cdr_upstream_rate \%hash;
- $record = new FS::cdr_upstream_rate { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cdr_upstream_rate object represents an upstream rate mapping to
-internal rate detail (see L<FS::rate_detail>). FS::cdr_upstream_rate inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item upstreamratenum - primary key
-
-=item upstream_rateid - CDR upstream Rate ID (cdr.upstream_rateid - see L<FS::cdr>)
-
-=item ratedetailnum - Rate detail - see L<FS::rate_detail>
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new upstream rate mapping. To add the upstream rate to the database,
-see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cdr_upstream_rate'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid upstream rate. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('upstreamratenum')
- #|| $self->ut_number('upstream_rateid')
- || $self->ut_alpha('upstream_rateid')
- #|| $self->ut_text('upstream_rateid')
- || $self->ut_foreign_key('ratedetailnum', 'rate_detail', 'ratedetailnum' )
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item rate_detail
-
-Returns the internal rate detail object for this upstream rate (see
-L<FS::rate_detail>).
-
-=cut
-
-sub rate_detail {
- my $self = shift;
- qsearchs('rate_detail', { 'ratedetailnum' => $self->ratedetailnum } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/clientapi_session.pm b/FS/FS/clientapi_session.pm
deleted file mode 100644
index f71a126..0000000
--- a/FS/FS/clientapi_session.pm
+++ /dev/null
@@ -1,121 +0,0 @@
-package FS::clientapi_session;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::clientapi_session - Object methods for clientapi_session records
-
-=head1 SYNOPSIS
-
- use FS::clientapi_session;
-
- $record = new FS::clientapi_session \%hash;
- $record = new FS::clientapi_session { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::clientapi_session object represents an FS::ClientAPI session.
-FS::clientapi_session inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item sessionnum - primary key
-
-=item sessionid - session ID
-
-=item namespace - session namespace
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'clientapi_session'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('primary_key')
- || $self->ut_number('validate_other_fields')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::ClientAPI>, <FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/clientapi_session_field.pm b/FS/FS/clientapi_session_field.pm
deleted file mode 100644
index bfa487d..0000000
--- a/FS/FS/clientapi_session_field.pm
+++ /dev/null
@@ -1,126 +0,0 @@
-package FS::clientapi_session_field;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::clientapi_session_field - Object methods for clientapi_session_field records
-
-=head1 SYNOPSIS
-
- use FS::clientapi_session_field;
-
- $record = new FS::clientapi_session_field \%hash;
- $record = new FS::clientapi_session_field { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::clientapi_session_field object represents a FS::ClientAPI session data
-field. FS::clientapi_session_field inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item fieldnum - primary key
-
-=item sessionnum - Base ClientAPI sesison (see L<FS::clientapi_session>)
-
-=item fieldname
-
-=item fieldvalie
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'clientapi_session_field'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('primary_key')
- || $self->ut_number('validate_other_fields')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-The author forgot to customize this manpage.
-
-=head1 SEE ALSO
-
-L<FS::clientapi_session>, L<FS::ClientAPI>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/conf.pm b/FS/FS/conf.pm
deleted file mode 100644
index 6126372..0000000
--- a/FS/FS/conf.pm
+++ /dev/null
@@ -1,114 +0,0 @@
-package FS::conf;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::conf - Object methods for conf records
-
-=head1 SYNOPSIS
-
- use FS::conf;
-
- $record = new FS::conf \%hash;
- $record = new FS::conf { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::conf object represents a configuration value. FS::conf inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item confnum - primary key
-
-=item agentnum - the agent to which this configuration value applies
-
-=item name - the name of the configuration value
-
-=item value - the configuration value
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new configuration value. To add the example to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'conf'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-=item check
-
-Checks all fields to make sure this is a valid configuration value. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('confnum')
- || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
- || $self->ut_text('name')
- || $self->ut_anything('value')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
deleted file mode 100644
index 0bc084d..0000000
--- a/FS/FS/cust_bill.pm
+++ /dev/null
@@ -1,2888 +0,0 @@
-package FS::cust_bill;
-
-use strict;
-use vars qw( @ISA $DEBUG $me $conf $money_char );
-use vars qw( $invoice_lines @buf ); #yuck
-use Fcntl qw(:flock); #for spool_csv
-use List::Util qw(min max);
-use Date::Format;
-use Text::Template 1.20;
-use File::Temp 0.14;
-use String::ShellQuote;
-use HTML::Entities;
-use Locale::Country;
-use FS::UID qw( datasrc );
-use FS::Misc qw( send_email send_fax generate_ps do_print );
-use FS::Record qw( qsearch qsearchs dbh );
-use FS::cust_main_Mixin;
-use FS::cust_main;
-use FS::cust_bill_pkg;
-use FS::cust_credit;
-use FS::cust_pay;
-use FS::cust_pkg;
-use FS::cust_credit_bill;
-use FS::pay_batch;
-use FS::cust_pay_batch;
-use FS::cust_bill_event;
-use FS::cust_event;
-use FS::part_pkg;
-use FS::cust_bill_pay;
-use FS::cust_bill_pay_batch;
-use FS::part_bill_event;
-use FS::payby;
-
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
-$DEBUG = 0;
-$me = '[FS::cust_bill]';
-
-#ask FS::UID to run this stuff for us later
-FS::UID->install_callback( sub {
- $conf = new FS::Conf;
- $money_char = $conf->config('money_char') || '$';
-} );
-
-=head1 NAME
-
-FS::cust_bill - Object methods for cust_bill records
-
-=head1 SYNOPSIS
-
- use FS::cust_bill;
-
- $record = new FS::cust_bill \%hash;
- $record = new FS::cust_bill { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
-
- @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
-
- ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
-
- @cust_pay_objects = $cust_bill->cust_pay;
-
- $tax_amount = $record->tax;
-
- @lines = $cust_bill->print_text;
- @lines = $cust_bill->print_text $time;
-
-=head1 DESCRIPTION
-
-An FS::cust_bill object represents an invoice; a declaration that a customer
-owes you money. The specific charges are itemized as B<cust_bill_pkg> records
-(see L<FS::cust_bill_pkg>). FS::cust_bill inherits from FS::Record. The
-following fields are currently supported:
-
-=over 4
-
-=item invnum - primary key (assigned automatically for new invoices)
-
-=item custnum - customer (see L<FS::cust_main>)
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item charged - amount of this invoice
-
-=item printed - deprecated
-
-=item closed - books closed flag, empty or `Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new invoice. To add the invoice to the database, see L<"insert">.
-Invoices are normally created by calling the bill method of a customer object
-(see L<FS::cust_main>).
-
-=cut
-
-sub table { 'cust_bill'; }
-
-sub cust_linked { $_[0]->cust_main_custnum; }
-sub cust_unlinked_msg {
- my $self = shift;
- "WARNING: can't find cust_main.custnum ". $self->custnum.
- ' (cust_bill.invnum '. $self->invnum. ')';
-}
-
-=item insert
-
-Adds this invoice to the database ("Posts" the invoice). If there is an error,
-returns the error, otherwise returns false.
-
-=item delete
-
-This method now works but you probably shouldn't use it. Instead, apply a
-credit against the invoice.
-
-Using this method to delete invoices outright is really, really bad. There
-would be no record you ever posted this invoice, and there are no check to
-make sure charged = 0 or that there are no associated cust_bill_pkg records.
-
-Really, don't use it.
-
-=cut
-
-sub delete {
- my $self = shift;
- return "Can't delete closed invoice" if $self->closed =~ /^Y/i;
- $self->SUPER::delete(@_);
-}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-Only printed may be changed. printed is normally updated by calling the
-collect method of a customer object (see L<FS::cust_main>).
-
-=cut
-
-#replace can be inherited from Record.pm
-
-# replace_check is now the preferred way to #implement replace data checks
-# (so $object->replace() works without an argument)
-
-sub replace_check {
- my( $new, $old ) = ( shift, shift );
- return "Can't change custnum!" unless $old->custnum == $new->custnum;
- #return "Can't change _date!" unless $old->_date eq $new->_date;
- return "Can't change _date!" unless $old->_date == $new->_date;
- return "Can't change charged!" unless $old->charged == $new->charged
- || $old->charged == 0;
-
- '';
-}
-
-=item check
-
-Checks all fields to make sure this is a valid invoice. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('invnum')
- || $self->ut_number('custnum')
- || $self->ut_numbern('_date')
- || $self->ut_money('charged')
- || $self->ut_numbern('printed')
- || $self->ut_enum('closed', [ '', 'Y' ])
- ;
- return $error if $error;
-
- return "Unknown customer"
- unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-
- $self->_date(time) unless $self->_date;
-
- $self->printed(0) if $self->printed eq '';
-
- $self->SUPER::check;
-}
-
-=item previous
-
-Returns a list consisting of the total previous balance for this customer,
-followed by the previous outstanding invoices (as FS::cust_bill objects also).
-
-=cut
-
-sub previous {
- my $self = shift;
- my $total = 0;
- my @cust_bill = sort { $a->_date <=> $b->_date }
- grep { $_->owed != 0 && $_->_date < $self->_date }
- qsearch( 'cust_bill', { 'custnum' => $self->custnum } )
- ;
- foreach ( @cust_bill ) { $total += $_->owed; }
- $total, @cust_bill;
-}
-
-=item cust_bill_pkg
-
-Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
-
-=cut
-
-sub cust_bill_pkg {
- my $self = shift;
- qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
-}
-
-=item cust_pkg
-
-Returns the packages (see L<FS::cust_pkg>) corresponding to the line items for
-this invoice.
-
-=cut
-
-sub cust_pkg {
- my $self = shift;
- my @cust_pkg = map { $_->cust_pkg } $self->cust_bill_pkg;
- my %saw = ();
- grep { ! $saw{$_->pkgnum}++ } @cust_pkg;
-}
-
-=item open_cust_bill_pkg
-
-Returns the open line items for this invoice.
-
-Note that cust_bill_pkg with both setup and recur fees are returned as two
-separate line items, each with only one fee.
-
-=cut
-
-# modeled after cust_main::open_cust_bill
-sub open_cust_bill_pkg {
- my $self = shift;
-
- # grep { $_->owed > 0 } $self->cust_bill_pkg
-
- my %other = ( 'recur' => 'setup',
- 'setup' => 'recur', );
- my @open = ();
- foreach my $field ( qw( recur setup )) {
- push @open, map { $_->set( $other{$field}, 0 ); $_; }
- grep { $_->owed($field) > 0 }
- $self->cust_bill_pkg;
- }
-
- @open;
-}
-
-=item cust_bill_event
-
-Returns the completed invoice events (deprecated, old-style events - see L<FS::cust_bill_event>) for this invoice.
-
-=cut
-
-sub cust_bill_event {
- my $self = shift;
- qsearch( 'cust_bill_event', { 'invnum' => $self->invnum } );
-}
-
-=item num_cust_bill_event
-
-Returns the number of completed invoice events (deprecated, old-style events - see L<FS::cust_bill_event>) for this invoice.
-
-=cut
-
-sub num_cust_bill_event {
- my $self = shift;
- my $sql =
- "SELECT COUNT(*) FROM cust_bill_event WHERE invnum = ?";
- my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
- $sth->execute($self->invnum) or die $sth->errstr. " executing $sql";
- $sth->fetchrow_arrayref->[0];
-}
-
-=item cust_event
-
-Returns the new-style customer billing events (see L<FS::cust_event>) for this invoice.
-
-=cut
-
-#false laziness w/cust_pkg.pm
-sub cust_event {
- my $self = shift;
- qsearch({
- 'table' => 'cust_event',
- 'addl_from' => 'JOIN part_event USING ( eventpart )',
- 'hashref' => { 'tablenum' => $self->invnum },
- 'extra_sql' => " AND eventtable = 'cust_bill' ",
- });
-}
-
-=item num_cust_event
-
-Returns the number of new-style customer billing events (see L<FS::cust_event>) for this invoice.
-
-=cut
-
-#false laziness w/cust_pkg.pm
-sub num_cust_event {
- my $self = shift;
- my $sql =
- "SELECT COUNT(*) FROM cust_event JOIN part_event USING ( eventpart ) ".
- " WHERE tablenum = ? AND eventtable = 'cust_bill'";
- my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
- $sth->execute($self->invnum) or die $sth->errstr. " executing $sql";
- $sth->fetchrow_arrayref->[0];
-}
-
-=item cust_main
-
-Returns the customer (see L<FS::cust_main>) for this invoice.
-
-=cut
-
-sub cust_main {
- my $self = shift;
- qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-}
-
-=item cust_suspend_if_balance_over AMOUNT
-
-Suspends the customer associated with this invoice if the total amount owed on
-this invoice and all older invoices is greater than the specified amount.
-
-Returns a list: an empty list on success or a list of errors.
-
-=cut
-
-sub cust_suspend_if_balance_over {
- my( $self, $amount ) = ( shift, shift );
- my $cust_main = $self->cust_main;
- if ( $cust_main->total_owed_date($self->_date) < $amount ) {
- return ();
- } else {
- $cust_main->suspend(@_);
- }
-}
-
-=item cust_credit
-
-Depreciated. See the cust_credited method.
-
- #Returns a list consisting of the total previous credited (see
- #L<FS::cust_credit>) and unapplied for this customer, followed by the previous
- #outstanding credits (FS::cust_credit objects).
-
-=cut
-
-sub cust_credit {
- use Carp;
- croak "FS::cust_bill->cust_credit depreciated; see ".
- "FS::cust_bill->cust_credit_bill";
- #my $self = shift;
- #my $total = 0;
- #my @cust_credit = sort { $a->_date <=> $b->_date }
- # grep { $_->credited != 0 && $_->_date < $self->_date }
- # qsearch('cust_credit', { 'custnum' => $self->custnum } )
- #;
- #foreach (@cust_credit) { $total += $_->credited; }
- #$total, @cust_credit;
-}
-
-=item cust_pay
-
-Depreciated. See the cust_bill_pay method.
-
-#Returns all payments (see L<FS::cust_pay>) for this invoice.
-
-=cut
-
-sub cust_pay {
- use Carp;
- croak "FS::cust_bill->cust_pay depreciated; see FS::cust_bill->cust_bill_pay";
- #my $self = shift;
- #sort { $a->_date <=> $b->_date }
- # qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
- #;
-}
-
-=item cust_bill_pay
-
-Returns all payment applications (see L<FS::cust_bill_pay>) for this invoice.
-
-=cut
-
-sub cust_bill_pay {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum } );
-}
-
-=item cust_credited
-
-Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice.
-
-=cut
-
-sub cust_credited {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum } )
- ;
-}
-
-=item tax
-
-Returns the tax amount (see L<FS::cust_bill_pkg>) for this invoice.
-
-=cut
-
-sub tax {
- my $self = shift;
- my $total = 0;
- my @taxlines = qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum ,
- 'pkgnum' => 0 } );
- foreach (@taxlines) { $total += $_->setup; }
- $total;
-}
-
-=item owed
-
-Returns the amount owed (still outstanding) on this invoice, which is charged
-minus all payment applications (see L<FS::cust_bill_pay>) and credit
-applications (see L<FS::cust_credit_bill>).
-
-=cut
-
-sub owed {
- my $self = shift;
- my $balance = $self->charged;
- $balance -= $_->amount foreach ( $self->cust_bill_pay );
- $balance -= $_->amount foreach ( $self->cust_credited );
- $balance = sprintf( "%.2f", $balance);
- $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
- $balance;
-}
-
-=item apply_payments_and_credits
-
-=cut
-
-sub apply_payments_and_credits {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $self->select_for_update; #mutex
-
- my @payments = grep { $_->unapplied > 0 } $self->cust_main->cust_pay;
- my @credits = grep { $_->credited > 0 } $self->cust_main->cust_credit;
-
- while ( $self->owed > 0 and ( @payments || @credits ) ) {
-
- my $app = '';
- if ( @payments && @credits ) {
-
- #decide which goes first by weight of top (unapplied) line item
-
- my @open_lineitems = $self->open_cust_bill_pkg;
-
- my $max_pay_weight =
- max( map { $_->part_pkg->pay_weight || 0 }
- grep { $_ }
- map { $_->cust_pkg }
- @open_lineitems
- );
- my $max_credit_weight =
- max( map { $_->part_pkg->credit_weight || 0 }
- grep { $_ }
- map { $_->cust_pkg }
- @open_lineitems
- );
-
- #if both are the same... payments first? it has to be something
- if ( $max_pay_weight >= $max_credit_weight ) {
- $app = 'pay';
- } else {
- $app = 'credit';
- }
-
- } elsif ( @payments ) {
- $app = 'pay';
- } elsif ( @credits ) {
- $app = 'credit';
- } else {
- die "guru meditation #12 and 35";
- }
-
- if ( $app eq 'pay' ) {
-
- my $payment = shift @payments;
-
- $app = new FS::cust_bill_pay {
- 'paynum' => $payment->paynum,
- 'amount' => sprintf('%.2f', min( $payment->unapplied, $self->owed ) ),
- };
-
- } elsif ( $app eq 'credit' ) {
-
- my $credit = shift @credits;
-
- $app = new FS::cust_credit_bill {
- 'crednum' => $credit->crednum,
- 'amount' => sprintf('%.2f', min( $credit->credited, $self->owed ) ),
- };
-
- } else {
- die "guru meditation #12 and 35";
- }
-
- $app->invnum( $self->invnum );
-
- my $error = $app->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error inserting ". $app->table. " record: $error";
- }
- die $error if $error;
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-
-}
-
-=item generate_email PARAMHASH
-
-PARAMHASH can contain the following:
-
-=over 4
-
-=item from => sender address, required
-
-=item tempate => alternate template name, optional
-
-=item print_text => text attachment arrayref, optional
-
-=item subject => email subject, optional
-
-=back
-
-Returns an argument list to be passed to L<FS::Misc::send_email>.
-
-=cut
-
-use MIME::Entity;
-
-sub generate_email {
-
- my $self = shift;
- my %args = @_;
-
- my $me = '[FS::cust_bill::generate_email]';
-
- my %return = (
- 'from' => $args{'from'},
- 'subject' => (($args{'subject'}) ? $args{'subject'} : 'Invoice'),
- );
-
- if (ref($args{'to'}) eq 'ARRAY') {
- $return{'to'} = $args{'to'};
- } else {
- $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ }
- $self->cust_main->invoicing_list
- ];
- }
-
- if ( $conf->exists('invoice_html') ) {
-
- warn "$me creating HTML/text multipart message"
- if $DEBUG;
-
- $return{'nobody'} = 1;
-
- my $alternative = build MIME::Entity
- 'Type' => 'multipart/alternative',
- 'Encoding' => '7bit',
- 'Disposition' => 'inline'
- ;
-
- my $data;
- if ( $conf->exists('invoice_email_pdf')
- and scalar($conf->config('invoice_email_pdf_note')) ) {
-
- warn "$me using 'invoice_email_pdf_note' in multipart message"
- if $DEBUG;
- $data = [ map { $_ . "\n" }
- $conf->config('invoice_email_pdf_note')
- ];
-
- } else {
-
- warn "$me not using 'invoice_email_pdf_note' in multipart message"
- if $DEBUG;
- if ( ref($args{'print_text'}) eq 'ARRAY' ) {
- $data = $args{'print_text'};
- } else {
- $data = [ $self->print_text('', $args{'template'}) ];
- }
-
- }
-
- $alternative->attach(
- 'Type' => 'text/plain',
- #'Encoding' => 'quoted-printable',
- 'Encoding' => '7bit',
- 'Data' => $data,
- 'Disposition' => 'inline',
- );
-
- $args{'from'} =~ /\@([\w\.\-]+)/;
- my $from = $1 || 'example.com';
- my $content_id = join('.', rand()*(2**32), $$, time). "\@$from";
-
- my $path = "$FS::UID::conf_dir/conf.$FS::UID::datasrc";
- my $file;
- if ( defined($args{'template'}) && length($args{'template'})
- && -e "$path/logo_". $args{'template'}. ".png"
- )
- {
- $file = "$path/logo_". $args{'template'}. ".png";
- } else {
- $file = "$path/logo.png";
- }
-
- my $image = build MIME::Entity
- 'Type' => 'image/png',
- 'Encoding' => 'base64',
- 'Path' => $file,
- 'Filename' => 'logo.png',
- 'Content-ID' => "<$content_id>",
- ;
-
- $alternative->attach(
- 'Type' => 'text/html',
- 'Encoding' => 'quoted-printable',
- 'Data' => [ '<html>',
- ' <head>',
- ' <title>',
- ' '. encode_entities($return{'subject'}),
- ' </title>',
- ' </head>',
- ' <body bgcolor="#e8e8e8">',
- $self->print_html('', $args{'template'}, $content_id),
- ' </body>',
- '</html>',
- ],
- 'Disposition' => 'inline',
- #'Filename' => 'invoice.pdf',
- );
-
- if ( $conf->exists('invoice_email_pdf') ) {
-
- #attaching pdf too:
- # multipart/mixed
- # multipart/related
- # multipart/alternative
- # text/plain
- # text/html
- # image/png
- # application/pdf
-
- my $related = build MIME::Entity 'Type' => 'multipart/related',
- 'Encoding' => '7bit';
-
- #false laziness w/Misc::send_email
- $related->head->replace('Content-type',
- $related->mime_type.
- '; boundary="'. $related->head->multipart_boundary. '"'.
- '; type=multipart/alternative'
- );
-
- $related->add_part($alternative);
-
- $related->add_part($image);
-
- my $pdf = build MIME::Entity $self->mimebuild_pdf('', $args{'template'});
-
- $return{'mimeparts'} = [ $related, $pdf ];
-
- } else {
-
- #no other attachment:
- # multipart/related
- # multipart/alternative
- # text/plain
- # text/html
- # image/png
-
- $return{'content-type'} = 'multipart/related';
- $return{'mimeparts'} = [ $alternative, $image ];
- $return{'type'} = 'multipart/alternative'; #Content-Type of first part...
- #$return{'disposition'} = 'inline';
-
- }
-
- } else {
-
- if ( $conf->exists('invoice_email_pdf') ) {
- warn "$me creating PDF attachment"
- if $DEBUG;
-
- #mime parts arguments a la MIME::Entity->build().
- $return{'mimeparts'} = [
- { $self->mimebuild_pdf('', $args{'template'}) }
- ];
- }
-
- if ( $conf->exists('invoice_email_pdf')
- and scalar($conf->config('invoice_email_pdf_note')) ) {
-
- warn "$me using 'invoice_email_pdf_note'"
- if $DEBUG;
- $return{'body'} = [ map { $_ . "\n" }
- $conf->config('invoice_email_pdf_note')
- ];
-
- } else {
-
- warn "$me not using 'invoice_email_pdf_note'"
- if $DEBUG;
- if ( ref($args{'print_text'}) eq 'ARRAY' ) {
- $return{'body'} = $args{'print_text'};
- } else {
- $return{'body'} = [ $self->print_text('', $args{'template'}) ];
- }
-
- }
-
- }
-
- %return;
-
-}
-
-=item mimebuild_pdf
-
-Returns a list suitable for passing to MIME::Entity->build(), representing
-this invoice as PDF attachment.
-
-=cut
-
-sub mimebuild_pdf {
- my $self = shift;
- (
- 'Type' => 'application/pdf',
- 'Encoding' => 'base64',
- 'Data' => [ $self->print_pdf(@_) ],
- 'Disposition' => 'attachment',
- 'Filename' => 'invoice.pdf',
- );
-}
-
-=item send [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
-
-Sends this invoice to the destinations configured for this customer: sends
-email, prints and/or faxes. See L<FS::cust_main_invoice>.
-
-TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
-
-AGENTNUM, if specified, means that this invoice will only be sent for customers
-of the specified agent or agent(s). AGENTNUM can be a scalar agentnum (for a
-single agent) or an arrayref of agentnums.
-
-INVOICE_FROM, if specified, overrides the default email invoice From: address.
-
-=cut
-
-sub queueable_send {
- my %opt = @_;
-
- my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } )
- or die "invalid invoice number: " . $opt{invnum};
-
- my @args = ( $opt{template}, $opt{agentnum} );
- push @args, $opt{invoice_from}
- if exists($opt{invoice_from}) && $opt{invoice_from};
-
- my $error = $self->send( @args );
- die $error if $error;
-
-}
-
-sub send {
- my $self = shift;
- my $template = scalar(@_) ? shift : '';
- if ( scalar(@_) && $_[0] ) {
- my $agentnums = ref($_[0]) ? shift : [ shift ];
- return 'N/A' unless grep { $_ == $self->cust_main->agentnum } @$agentnums;
- }
-
- my $invoice_from =
- scalar(@_)
- ? shift
- : ( $self->_agent_invoice_from || $conf->config('invoice_from') );
-
- my @invoicing_list = $self->cust_main->invoicing_list;
-
- $self->email($template, $invoice_from)
- if grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list;
-
- $self->print($template)
- if grep { $_ eq 'POST' } @invoicing_list; #postal
-
- $self->fax($template)
- if grep { $_ eq 'FAX' } @invoicing_list; #fax
-
- '';
-
-}
-
-=item email [ TEMPLATENAME [ , INVOICE_FROM ] ]
-
-Emails this invoice.
-
-TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
-
-INVOICE_FROM, if specified, overrides the default email invoice From: address.
-
-=cut
-
-sub queueable_email {
- my %opt = @_;
-
- my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } )
- or die "invalid invoice number: " . $opt{invnum};
-
- my @args = ( $opt{template} );
- push @args, $opt{invoice_from}
- if exists($opt{invoice_from}) && $opt{invoice_from};
-
- my $error = $self->email( @args );
- die $error if $error;
-
-}
-
-sub email {
- my $self = shift;
- my $template = scalar(@_) ? shift : '';
- my $invoice_from =
- scalar(@_)
- ? shift
- : ( $self->_agent_invoice_from || $conf->config('invoice_from') );
-
- my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ }
- $self->cust_main->invoicing_list;
-
- #better to notify this person than silence
- @invoicing_list = ($invoice_from) unless @invoicing_list;
-
- my $error = send_email(
- $self->generate_email(
- 'from' => $invoice_from,
- 'to' => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ],
- 'template' => $template,
- )
- );
- die "can't email invoice: $error\n" if $error;
- #die "$error\n" if $error;
-
-}
-
-=item lpr_data [ TEMPLATENAME ]
-
-Returns the postscript or plaintext for this invoice as an arrayref.
-
-TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
-
-=cut
-
-sub lpr_data {
- my( $self, $template) = @_;
- $conf->exists('invoice_latex')
- ? [ $self->print_ps('', $template) ]
- : [ $self->print_text('', $template) ];
-}
-
-=item print [ TEMPLATENAME ]
-
-Prints this invoice.
-
-TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
-
-=cut
-
-sub print {
- my $self = shift;
- my $template = scalar(@_) ? shift : '';
-
- do_print $self->lpr_data($template);
-}
-
-=item fax [ TEMPLATENAME ]
-
-Faxes this invoice.
-
-TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
-
-=cut
-
-sub fax {
- my $self = shift;
- my $template = scalar(@_) ? shift : '';
-
- die 'FAX invoice destination not (yet?) supported with plain text invoices.'
- unless $conf->exists('invoice_latex');
-
- my $dialstring = $self->cust_main->getfield('fax');
- #Check $dialstring?
-
- my $error = send_fax( 'docdata' => $self->lpr_data($template),
- 'dialstring' => $dialstring,
- );
- die $error if $error;
-
-}
-
-=item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
-
-Like B<send>, but only sends the invoice if it is the newest open invoice for
-this customer.
-
-=cut
-
-sub send_if_newest {
- my $self = shift;
-
- return ''
- if scalar(
- grep { $_->owed > 0 }
- qsearch('cust_bill', {
- 'custnum' => $self->custnum,
- #'_date' => { op=>'>', value=>$self->_date },
- 'invnum' => { op=>'>', value=>$self->invnum },
- } )
- );
-
- $self->send(@_);
-}
-
-=item send_csv OPTION => VALUE, ...
-
-Sends invoice as a CSV data-file to a remote host with the specified protocol.
-
-Options are:
-
-protocol - currently only "ftp"
-server
-username
-password
-dir
-
-The file will be named "N-YYYYMMDDHHMMSS.csv" where N is the invoice number
-and YYMMDDHHMMSS is a timestamp.
-
-See L</print_csv> for a description of the output format.
-
-=cut
-
-sub send_csv {
- my($self, %opt) = @_;
-
- #create file(s)
-
- my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill";
- mkdir $spooldir, 0700 unless -d $spooldir;
-
- my $tracctnum = $self->invnum. time2str('-%Y%m%d%H%M%S', time);
- my $file = "$spooldir/$tracctnum.csv";
-
- my ( $header, $detail ) = $self->print_csv(%opt, 'tracctnum' => $tracctnum );
-
- open(CSV, ">$file") or die "can't open $file: $!";
- print CSV $header;
-
- print CSV $detail;
-
- close CSV;
-
- my $net;
- if ( $opt{protocol} eq 'ftp' ) {
- eval "use Net::FTP;";
- die $@ if $@;
- $net = Net::FTP->new($opt{server}) or die @$;
- } else {
- die "unknown protocol: $opt{protocol}";
- }
-
- $net->login( $opt{username}, $opt{password} )
- or die "can't FTP to $opt{username}\@$opt{server}: login error: $@";
-
- $net->binary or die "can't set binary mode";
-
- $net->cwd($opt{dir}) or die "can't cwd to $opt{dir}";
-
- $net->put($file) or die "can't put $file: $!";
-
- $net->quit;
-
- unlink $file;
-
-}
-
-=item spool_csv
-
-Spools CSV invoice data.
-
-Options are:
-
-=over 4
-
-=item format - 'default' or 'billco'
-
-=item dest - if set (to POST, EMAIL or FAX), only sends spools invoices if the customer has the corresponding invoice destinations set (see L<FS::cust_main_invoice>).
-
-=item agent_spools - if set to a true value, will spool to per-agent files rather than a single global file
-
-=item balanceover - if set, only spools the invoice if the total amount owed on this invoice and all older invoices is greater than the specified amount.
-
-=back
-
-=cut
-
-sub spool_csv {
- my($self, %opt) = @_;
-
- my $cust_main = $self->cust_main;
-
- if ( $opt{'dest'} ) {
- my %invoicing_list = map { /^(POST|FAX)$/ or 'EMAIL' =~ /^(.*)$/; $1 => 1 }
- $cust_main->invoicing_list;
- return 'N/A' unless $invoicing_list{$opt{'dest'}}
- || ! keys %invoicing_list;
- }
-
- if ( $opt{'balanceover'} ) {
- return 'N/A'
- if $cust_main->total_owed_date($self->_date) < $opt{'balanceover'};
- }
-
- my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill";
- mkdir $spooldir, 0700 unless -d $spooldir;
-
- my $tracctnum = $self->invnum. time2str('-%Y%m%d%H%M%S', time);
-
- my $file =
- "$spooldir/".
- ( $opt{'agent_spools'} ? 'agentnum'.$cust_main->agentnum : 'spool' ).
- ( lc($opt{'format'}) eq 'billco' ? '-header' : '' ) .
- '.csv';
-
- my ( $header, $detail ) = $self->print_csv(%opt, 'tracctnum' => $tracctnum );
-
- open(CSV, ">>$file") or die "can't open $file: $!";
- flock(CSV, LOCK_EX);
- seek(CSV, 0, 2);
-
- print CSV $header;
-
- if ( lc($opt{'format'}) eq 'billco' ) {
-
- flock(CSV, LOCK_UN);
- close CSV;
-
- $file =
- "$spooldir/".
- ( $opt{'agent_spools'} ? 'agentnum'.$cust_main->agentnum : 'spool' ).
- '-detail.csv';
-
- open(CSV,">>$file") or die "can't open $file: $!";
- flock(CSV, LOCK_EX);
- seek(CSV, 0, 2);
- }
-
- print CSV $detail;
-
- flock(CSV, LOCK_UN);
- close CSV;
-
- return '';
-
-}
-
-=item print_csv OPTION => VALUE, ...
-
-Returns CSV data for this invoice.
-
-Options are:
-
-format - 'default' or 'billco'
-
-Returns a list consisting of two scalars. The first is a single line of CSV
-header information for this invoice. The second is one or more lines of CSV
-detail information for this invoice.
-
-If I<format> is not specified or "default", the fields of the CSV file are as
-follows:
-
-record_type, invnum, custnum, _date, charged, first, last, company, address1, address2, city, state, zip, country, pkg, setup, recur, sdate, edate
-
-=over 4
-
-=item record type - B<record_type> is either C<cust_bill> or C<cust_bill_pkg>
-
-B<record_type> is C<cust_bill> for the initial header line only. The
-last five fields (B<pkg> through B<edate>) are irrelevant, and all other
-fields are filled in.
-
-B<record_type> is C<cust_bill_pkg> for detail lines. Only the first two fields
-(B<record_type> and B<invnum>) and the last five fields (B<pkg> through B<edate>)
-are filled in.
-
-=item invnum - invoice number
-
-=item custnum - customer number
-
-=item _date - invoice date
-
-=item charged - total invoice amount
-
-=item first - customer first name
-
-=item last - customer first name
-
-=item company - company name
-
-=item address1 - address line 1
-
-=item address2 - address line 1
-
-=item city
-
-=item state
-
-=item zip
-
-=item country
-
-=item pkg - line item description
-
-=item setup - line item setup fee (one or both of B<setup> and B<recur> will be defined)
-
-=item recur - line item recurring fee (one or both of B<setup> and B<recur> will be defined)
-
-=item sdate - start date for recurring fee
-
-=item edate - end date for recurring fee
-
-=back
-
-If I<format> is "billco", the fields of the header CSV file are as follows:
-
- +-------------------------------------------------------------------+
- | FORMAT HEADER FILE |
- |-------------------------------------------------------------------|
- | Field | Description | Name | Type | Width |
- | 1 | N/A-Leave Empty | RC | CHAR | 2 |
- | 2 | N/A-Leave Empty | CUSTID | CHAR | 15 |
- | 3 | Transaction Account No | TRACCTNUM | CHAR | 15 |
- | 4 | Transaction Invoice No | TRINVOICE | CHAR | 15 |
- | 5 | Transaction Zip Code | TRZIP | CHAR | 5 |
- | 6 | Transaction Company Bill To | TRCOMPANY | CHAR | 30 |
- | 7 | Transaction Contact Bill To | TRNAME | CHAR | 30 |
- | 8 | Additional Address Unit Info | TRADDR1 | CHAR | 30 |
- | 9 | Bill To Street Address | TRADDR2 | CHAR | 30 |
- | 10 | Ancillary Billing Information | TRADDR3 | CHAR | 30 |
- | 11 | Transaction City Bill To | TRCITY | CHAR | 20 |
- | 12 | Transaction State Bill To | TRSTATE | CHAR | 2 |
- | 13 | Bill Cycle Close Date | CLOSEDATE | CHAR | 10 |
- | 14 | Bill Due Date | DUEDATE | CHAR | 10 |
- | 15 | Previous Balance | BALFWD | NUM* | 9 |
- | 16 | Pmt/CR Applied | CREDAPPLY | NUM* | 9 |
- | 17 | Total Current Charges | CURRENTCHG | NUM* | 9 |
- | 18 | Total Amt Due | TOTALDUE | NUM* | 9 |
- | 19 | Total Amt Due | AMTDUE | NUM* | 9 |
- | 20 | 30 Day Aging | AMT30 | NUM* | 9 |
- | 21 | 60 Day Aging | AMT60 | NUM* | 9 |
- | 22 | 90 Day Aging | AMT90 | NUM* | 9 |
- | 23 | Y/N | AGESWITCH | CHAR | 1 |
- | 24 | Remittance automation | SCANLINE | CHAR | 100 |
- | 25 | Total Taxes & Fees | TAXTOT | NUM* | 9 |
- | 26 | Customer Reference Number | CUSTREF | CHAR | 15 |
- | 27 | Federal Tax*** | FEDTAX | NUM* | 9 |
- | 28 | State Tax*** | STATETAX | NUM* | 9 |
- | 29 | Other Taxes & Fees*** | OTHERTAX | NUM* | 9 |
- +-------+-------------------------------+------------+------+-------+
-
-If I<format> is "billco", the fields of the detail CSV file are as follows:
-
- FORMAT FOR DETAIL FILE
- | | | |
- Field | Description | Name | Type | Width
- 1 | N/A-Leave Empty | RC | CHAR | 2
- 2 | N/A-Leave Empty | CUSTID | CHAR | 15
- 3 | Account Number | TRACCTNUM | CHAR | 15
- 4 | Invoice Number | TRINVOICE | CHAR | 15
- 5 | Line Sequence (sort order) | LINESEQ | NUM | 6
- 6 | Transaction Detail | DETAILS | CHAR | 100
- 7 | Amount | AMT | NUM* | 9
- 8 | Line Format Control** | LNCTRL | CHAR | 2
- 9 | Grouping Code | GROUP | CHAR | 2
- 10 | User Defined | ACCT CODE | CHAR | 15
-
-=cut
-
-sub print_csv {
- my($self, %opt) = @_;
-
- eval "use Text::CSV_XS";
- die $@ if $@;
-
- my $cust_main = $self->cust_main;
-
- my $csv = Text::CSV_XS->new({'always_quote'=>1});
-
- if ( lc($opt{'format'}) eq 'billco' ) {
-
- my $taxtotal = 0;
- $taxtotal += $_->{'amount'} foreach $self->_items_tax;
-
- my $duedate = $self->due_date2str('%m/%d/%Y'); #date_format?
-
- my( $previous_balance, @unused ) = $self->previous; #previous balance
-
- my $pmt_cr_applied = 0;
- $pmt_cr_applied += $_->{'amount'}
- foreach ( $self->_items_payments, $self->_items_credits ) ;
-
- my $totaldue = sprintf('%.2f', $self->owed + $previous_balance);
-
- $csv->combine(
- '', # 1 | N/A-Leave Empty CHAR 2
- '', # 2 | N/A-Leave Empty CHAR 15
- $opt{'tracctnum'}, # 3 | Transaction Account No CHAR 15
- $self->invnum, # 4 | Transaction Invoice No CHAR 15
- $cust_main->zip, # 5 | Transaction Zip Code CHAR 5
- $cust_main->company, # 6 | Transaction Company Bill To CHAR 30
- #$cust_main->payname, # 7 | Transaction Contact Bill To CHAR 30
- $cust_main->contact, # 7 | Transaction Contact Bill To CHAR 30
- $cust_main->address2, # 8 | Additional Address Unit Info CHAR 30
- $cust_main->address1, # 9 | Bill To Street Address CHAR 30
- '', # 10 | Ancillary Billing Information CHAR 30
- $cust_main->city, # 11 | Transaction City Bill To CHAR 20
- $cust_main->state, # 12 | Transaction State Bill To CHAR 2
-
- # XXX ?
- time2str("%m/%d/%Y", $self->_date), # 13 | Bill Cycle Close Date CHAR 10
-
- # XXX ?
- $duedate, # 14 | Bill Due Date CHAR 10
-
- $previous_balance, # 15 | Previous Balance NUM* 9
- $pmt_cr_applied, # 16 | Pmt/CR Applied NUM* 9
- sprintf("%.2f", $self->charged), # 17 | Total Current Charges NUM* 9
- $totaldue, # 18 | Total Amt Due NUM* 9
- $totaldue, # 19 | Total Amt Due NUM* 9
- '', # 20 | 30 Day Aging NUM* 9
- '', # 21 | 60 Day Aging NUM* 9
- '', # 22 | 90 Day Aging NUM* 9
- 'N', # 23 | Y/N CHAR 1
- '', # 24 | Remittance automation CHAR 100
- $taxtotal, # 25 | Total Taxes & Fees NUM* 9
- $self->custnum, # 26 | Customer Reference Number CHAR 15
- '0', # 27 | Federal Tax*** NUM* 9
- sprintf("%.2f", $taxtotal), # 28 | State Tax*** NUM* 9
- '0', # 29 | Other Taxes & Fees*** NUM* 9
- );
-
- } else {
-
- $csv->combine(
- 'cust_bill',
- $self->invnum,
- $self->custnum,
- time2str("%x", $self->_date),
- sprintf("%.2f", $self->charged),
- ( map { $cust_main->getfield($_) }
- qw( first last company address1 address2 city state zip country ) ),
- map { '' } (1..5),
- ) or die "can't create csv";
- }
-
- my $header = $csv->string. "\n";
-
- my $detail = '';
- if ( lc($opt{'format'}) eq 'billco' ) {
-
- my $lineseq = 0;
- foreach my $item ( $self->_items_pkg ) {
-
- $csv->combine(
- '', # 1 | N/A-Leave Empty CHAR 2
- '', # 2 | N/A-Leave Empty CHAR 15
- $opt{'tracctnum'}, # 3 | Account Number CHAR 15
- $self->invnum, # 4 | Invoice Number CHAR 15
- $lineseq++, # 5 | Line Sequence (sort order) NUM 6
- $item->{'description'}, # 6 | Transaction Detail CHAR 100
- $item->{'amount'}, # 7 | Amount NUM* 9
- '', # 8 | Line Format Control** CHAR 2
- '', # 9 | Grouping Code CHAR 2
- '', # 10 | User Defined CHAR 15
- );
-
- $detail .= $csv->string. "\n";
-
- }
-
- } else {
-
- foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
-
- my($pkg, $setup, $recur, $sdate, $edate);
- if ( $cust_bill_pkg->pkgnum ) {
-
- ($pkg, $setup, $recur, $sdate, $edate) = (
- $cust_bill_pkg->cust_pkg->part_pkg->pkg,
- ( $cust_bill_pkg->setup != 0
- ? sprintf("%.2f", $cust_bill_pkg->setup )
- : '' ),
- ( $cust_bill_pkg->recur != 0
- ? sprintf("%.2f", $cust_bill_pkg->recur )
- : '' ),
- ( $cust_bill_pkg->sdate
- ? time2str("%x", $cust_bill_pkg->sdate)
- : '' ),
- ($cust_bill_pkg->edate
- ?time2str("%x", $cust_bill_pkg->edate)
- : '' ),
- );
-
- } else { #pkgnum tax
- next unless $cust_bill_pkg->setup != 0;
- my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc')
- ? ( $cust_bill_pkg->itemdesc || 'Tax' )
- : 'Tax';
- ($pkg, $setup, $recur, $sdate, $edate) =
- ( $itemdesc, sprintf("%10.2f",$cust_bill_pkg->setup), '', '', '' );
- }
-
- $csv->combine(
- 'cust_bill_pkg',
- $self->invnum,
- ( map { '' } (1..11) ),
- ($pkg, $setup, $recur, $sdate, $edate)
- ) or die "can't create csv";
-
- $detail .= $csv->string. "\n";
-
- }
-
- }
-
- ( $header, $detail );
-
-}
-
-=item comp
-
-Pays this invoice with a compliemntary payment. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub comp {
- my $self = shift;
- my $cust_pay = new FS::cust_pay ( {
- 'invnum' => $self->invnum,
- 'paid' => $self->owed,
- '_date' => '',
- 'payby' => 'COMP',
- 'payinfo' => $self->cust_main->payinfo,
- 'paybatch' => '',
- } );
- $cust_pay->insert;
-}
-
-=item realtime_card
-
-Attempts to pay this invoice with a credit card payment via a
-Business::OnlinePayment realtime gateway. See
-http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
-for supported processors.
-
-=cut
-
-sub realtime_card {
- my $self = shift;
- $self->realtime_bop( 'CC', @_ );
-}
-
-=item realtime_ach
-
-Attempts to pay this invoice with an electronic check (ACH) payment via a
-Business::OnlinePayment realtime gateway. See
-http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
-for supported processors.
-
-=cut
-
-sub realtime_ach {
- my $self = shift;
- $self->realtime_bop( 'ECHECK', @_ );
-}
-
-=item realtime_lec
-
-Attempts to pay this invoice with phone bill (LEC) payment via a
-Business::OnlinePayment realtime gateway. See
-http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
-for supported processors.
-
-=cut
-
-sub realtime_lec {
- my $self = shift;
- $self->realtime_bop( 'LEC', @_ );
-}
-
-sub realtime_bop {
- my( $self, $method ) = @_;
-
- my $cust_main = $self->cust_main;
- my $balance = $cust_main->balance;
- my $amount = ( $balance < $self->owed ) ? $balance : $self->owed;
- $amount = sprintf("%.2f", $amount);
- return "not run (balance $balance)" unless $amount > 0;
-
- my $description = 'Internet Services';
- if ( $conf->exists('business-onlinepayment-description') ) {
- my $dtempl = $conf->config('business-onlinepayment-description');
-
- my $agent_obj = $cust_main->agent
- or die "can't retreive agent for $cust_main (agentnum ".
- $cust_main->agentnum. ")";
- my $agent = $agent_obj->agent;
- my $pkgs = join(', ',
- map { $_->cust_pkg->part_pkg->pkg }
- grep { $_->pkgnum } $self->cust_bill_pkg
- );
- $description = eval qq("$dtempl");
- }
-
- $cust_main->realtime_bop($method, $amount,
- 'description' => $description,
- 'invnum' => $self->invnum,
- );
-
-}
-
-=item batch_card OPTION => VALUE...
-
-Adds a payment for this invoice to the pending credit card batch (see
-L<FS::cust_pay_batch>), or, if the B<realtime> option is set to a true value,
-runs the payment using a realtime gateway.
-
-=cut
-
-sub batch_card {
- my ($self, %options) = @_;
- my $cust_main = $self->cust_main;
-
- $options{invnum} = $self->invnum;
-
- $cust_main->batch_card(%options);
-}
-
-sub _agent_template {
- my $self = shift;
- $self->cust_main->agent_template;
-}
-
-sub _agent_invoice_from {
- my $self = shift;
- $self->cust_main->agent_invoice_from;
-}
-
-=item print_text [ TIME [ , TEMPLATE ] ]
-
-Returns an text invoice, as a list of lines.
-
-TIME an optional value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-sub print_text {
- my( $self, $today, $template ) = @_;
-
- my %params = ( 'format' => 'template' );
- $params{'time'} = $today if $today;
- $params{'template'} = $template if $template;
-
- $self->print_generic( %params );
-}
-
-=item print_latex [ TIME [ , TEMPLATE ] ]
-
-Internal method - returns a filename of a filled-in LaTeX template for this
-invoice (Note: add ".tex" to get the actual filename), and a filename of
-an associated logo (with the .eps extension included).
-
-See print_ps and print_pdf for methods that return PostScript and PDF output.
-
-TIME an optional value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-sub print_latex {
-
- my( $self, $today, $template ) = @_;
-
- my %params = ( 'format' => 'latex' );
- $params{'time'} = $today if $today;
- $params{'template'} = $template if $template;
-
- $template ||= $self->_agent_template;
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- my $lh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX',
- DIR => $dir,
- SUFFIX => '.eps',
- UNLINK => 0,
- ) or die "can't open temp file: $!\n";
-
- if ($template && $conf->exists("logo_${template}.eps")) {
- print $lh $conf->config_binary("logo_${template}.eps")
- or die "can't write temp file: $!\n";
- }else{
- print $lh $conf->config_binary('logo.eps')
- or die "can't write temp file: $!\n";
- }
- close $lh;
- $params{'logo_file'} = $lh->filename;
-
- my @filled_in = $self->print_generic( %params );
-
- my $fh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX',
- DIR => $dir,
- SUFFIX => '.tex',
- UNLINK => 0,
- ) or die "can't open temp file: $!\n";
- print $fh join('', @filled_in );
- close $fh;
-
- $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
- return ($1, $params{'logo_file'});
-
-}
-
-=item print_generic OPTIONS_HASH
-
-Internal method - returns a filled-in template for this invoice as a scalar.
-
-See print_ps and print_pdf for methods that return PostScript and PDF output.
-
-Non optional options include
- format - latex, html, template
-
-Optional options include
-
-template - a value used as a suffix for a configuration template
-
-time - a value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-cid -
-
-=cut
-
-sub print_generic {
-
- my( $self, %params ) = @_;
- my $today = $params{today} ? $params{today} : time;
- warn "FS::cust_bill::print_generic called on $self with suffix $params{template}\n"
- if $DEBUG;
-
- my $format = $params{format};
- die "Unknown format: $format"
- unless $format =~ /^(latex|html|template)$/;
-
- my $cust_main = $self->cust_main;
- $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
- unless $cust_main->payname && $cust_main->payby !~ /^(CHEK|DCHK)$/;
-
-
- my %delimiters = ( 'latex' => [ '[@--', '--@]' ],
- 'html' => [ '<%=', '%>' ],
- 'template' => [ '{', '}' ],
- );
-
- #create the template
- my $template = $params{template} ? $params{template} : $self->_agent_template;
- my $templatefile = "invoice_$format";
- $templatefile .= "_$template"
- if length($template);
- my @invoice_template = map "$_\n", $conf->config($templatefile)
- or die "cannot load config file $templatefile";
-
- my $old_latex = '';
- if ( $format eq 'latex' && grep { /^%%Detail/ } @invoice_template ) {
- #change this to a die when the old code is removed
- warn "old-style invoice template $templatefile; ".
- "patch with conf/invoice_latex.diff or use new conf/invoice_latex*\n";
- $old_latex = 'true';
- @invoice_template = _translate_old_latex_format(@invoice_template);
- }
-
- my $text_template = new Text::Template(
- TYPE => 'ARRAY',
- SOURCE => \@invoice_template,
- DELIMITERS => $delimiters{$format},
- );
-
- $text_template->compile()
- or die 'While compiling ' . $templatefile . ': ' . $Text::Template::ERROR;
-
-
- # additional substitution could possibly cause breakage in existing templates
- my %convert_maps = (
- 'latex' => {
- 'notes' => sub { map "$_", @_ },
- 'footer' => sub { map "$_", @_ },
- 'smallfooter' => sub { map "$_", @_ },
- 'returnaddress' => sub { map "$_", @_ },
- },
- 'html' => {
- 'notes' =>
- sub {
- map {
- s/%%(.*)$/<!-- $1 -->/g;
- s/\\section\*\{\\textsc\{(.)(.*)\}\}/<p><b><font size="+1">$1<\/font>\U$2<\/b>/g;
- s/\\begin\{enumerate\}/<ol>/g;
- s/\\item / <li>/g;
- s/\\end\{enumerate\}/<\/ol>/g;
- s/\\textbf\{(.*)\}/<b>$1<\/b>/g;
- s/\\\\\*/<br>/g;
- s/\\dollar ?/\$/g;
- s/\\#/#/g;
- s/~/&nbsp;/g;
- $_;
- } @_
- },
- 'footer' =>
- sub { map { s/~/&nbsp;/g; s/\\\\\*?\s*$/<BR>/; $_; } @_ },
- 'smallfooter' =>
- sub { map { s/~/&nbsp;/g; s/\\\\\*?\s*$/<BR>/; $_; } @_ },
- 'returnaddress' =>
- sub {
- map {
- s/~/&nbsp;/g;
- s/\\\\\*?\s*$/<BR>/;
- s/\\hyphenation\{[\w\s\-]+}//;
- $_;
- } @_
- },
- },
- 'template' => {
- 'notes' =>
- sub {
- map {
- s/%%.*$//g;
- s/\\section\*\{\\textsc\{(.*)\}\}/\U$1/g;
- s/\\begin\{enumerate\}//g;
- s/\\item / * /g;
- s/\\end\{enumerate\}//g;
- s/\\textbf\{(.*)\}/$1/g;
- s/\\\\\*/ /;
- s/\\dollar ?/\$/g;
- $_;
- } @_
- },
- 'footer' =>
- sub { map { s/~/ /g; s/\\\\\*?\s*$/\n/; $_; } @_ },
- 'smallfooter' =>
- sub { map { s/~/ /g; s/\\\\\*?\s*$/\n/; $_; } @_ },
- 'returnaddress' =>
- sub {
- map {
- s/~/ /g;
- s/\\\\\*?\s*$/\n/; # dubious
- s/\\hyphenation\{[\w\s\-]+}//;
- $_;
- } @_
- },
- },
- );
-
-
- # hashes for differing output formats
- my %nbsps = ( 'latex' => '~',
- 'html' => '', # '&nbps;' would be nice
- 'template' => '', # not used
- );
- my $nbsp = $nbsps{$format};
-
- my %escape_functions = ( 'latex' => \&_latex_escape,
- 'html' => \&encode_entities,
- 'template' => sub { shift },
- );
- my $escape_function = $escape_functions{$format};
-
- my %date_formats = ( 'latex' => '%b, %o, %Y',
- 'html' => '%b&nbsp;%o,&nbsp;%Y',
- 'template' => '%s',
- );
- my $date_format = $date_formats{$format};
-
- my %embolden_functions = ( 'latex' => sub { return '\textbf{'. shift(). '}'
- },
- 'html' => sub { return '<b>'. shift(). '</b>'
- },
- 'template' => sub { shift },
- );
- my $embolden_function = $embolden_functions{$format};
-
-
- # generate template variables
- my $returnaddress;
- if (
- defined( $conf->config_orbase( "invoice_${format}returnaddress",
- $template
- )
- )
- && length( $conf->config_orbase( "invoice_${format}returnaddress",
- $template
- )
- )
- ) {
-
- $returnaddress = join("\n",
- $conf->config_orbase("invoice_${format}returnaddress", $template)
- );
-
- } elsif ( grep /\S/,
- $conf->config_orbase('invoice_latexreturnaddress', $template) ) {
-
- my $convert_map = $convert_maps{$format}{'returnaddress'};
- $returnaddress =
- join( "\n",
- &$convert_map( $conf->config_orbase( "invoice_latexreturnaddress",
- $template
- )
- )
- );
- } elsif ( grep /\S/, $conf->config('company_address') ) {
-
- $returnaddress = join( "\n", $conf->config('company_address') );
-
- $returnaddress =
- join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg,
- $conf->config('company_address')
- )
- if $format eq 'latex';
-
- } else {
-
- my $warning = "Couldn't find a return address; ".
- "do you need to set the company_address configuration value?";
- warn "$warning\n";
- $returnaddress = $nbsp;
- #$returnaddress = $warning;
-
- }
-
- my %invoice_data = (
- 'company_name' => scalar( $conf->config('company_name') ),
- 'company_address' => join("\n", $conf->config('company_address') ). "\n",
- 'custnum' => $self->custnum,
- 'invnum' => $self->invnum,
- 'date' => time2str($date_format, $self->_date),
- 'today' => time2str('%b %o, %Y', $today),
- 'agent' => &$escape_function($cust_main->agent->agent),
- 'payname' => &$escape_function($cust_main->payname),
- 'company' => &$escape_function($cust_main->company),
- 'address1' => &$escape_function($cust_main->address1),
- 'address2' => &$escape_function($cust_main->address2),
- 'city' => &$escape_function($cust_main->city),
- 'state' => &$escape_function($cust_main->state),
- 'zip' => &$escape_function($cust_main->zip),
- 'returnaddress' => $returnaddress,
- 'quantity' => 1,
- 'terms' => $self->terms,
- 'template' => $params{'template'},
- #'notes' => join("\n", $conf->config('invoice_latexnotes') ),
- # better hang on to conf_dir for a while
- 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
- 'page' => 1,
- 'total_pages' => 1,
- );
-
- $invoice_data{'cid'} = $params{'cid'}
- if $params{'cid'};
-
- my $countrydefault = $conf->config('countrydefault') || 'US';
- if ( $cust_main->country eq $countrydefault ) {
- $invoice_data{'country'} = '';
- } else {
- $invoice_data{'country'} = &$escape_function(code2country($cust_main->country));
- }
-
- my @address = ();
- $invoice_data{'address'} = \@address;
- push @address,
- $cust_main->payname.
- ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
- ? " (P.O. #". $cust_main->payinfo. ")"
- : ''
- )
- ;
- push @address, $cust_main->company
- if $cust_main->company;
- push @address, $cust_main->address1;
- push @address, $cust_main->address2
- if $cust_main->address2;
- push @address,
- $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip;
- push @address, $invoice_data{'country'}
- if $invoice_data{'country'};
- push @address, ''
- while (scalar(@address) < 5);
-
- #do variable substitution in notes, footer, smallfooter
- foreach my $include (qw( notes footer smallfooter )) {
-
- my @inc_src = $conf->config_orbase("invoice_latex$include", $template );
- my $convert_map = $convert_maps{$format}{$include};
-
- if (
- defined( $conf->config_orbase("invoice_${format}$include", $template) )
- && length( $conf->config_orbase('invoice_${format}$include', $template) )
- ) {
- @inc_src = $conf->config_orbase("invoice_${format}$include", $template );
- } else {
- @inc_src =
- map { s/\[@--/$delimiters{$format}[0]/g;
- s/--@]/$delimiters{$format}[1]/g;
- $_;
- }
- &$convert_map(
- $conf->config_orbase("invoice_latex$include", $template )
- );
- }
-
- my $inc_tt = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", @inc_src ],
- DELIMITERS => $delimiters{$format},
- ) or die "can't create new Text::Template object: $Text::Template::ERROR";
-
- $inc_tt->compile()
- or die "can't compile template: $Text::Template::ERROR";
-
- $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data );
-
- $invoice_data{$include} =~ s/\n+$//
- if ($format eq 'latex');
- }
-
- $invoice_data{'po_line'} =
- ( $cust_main->payby eq 'BILL' && $cust_main->payinfo )
- ? &$escape_function("Purchase Order #". $cust_main->payinfo)
- : $nbsp;
-
- my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
-# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
- #my $balance_due = $self->owed + $pr_total - $cr_total;
- my $balance_due = $self->owed + $pr_total;
-
- my %money_chars = ( 'latex' => '',
- 'html' => $conf->config('money_char') || '$',
- 'template' => '',
- );
- my $money_char = $money_chars{$format};
-
- my %other_money_chars = ( 'latex' => '\dollar ',
- 'html' => $conf->config('money_char') || '$',
- 'template' => '',
- );
- my $other_money_char = $other_money_chars{$format};
-
- my @detail_items = ();
- my @total_items = ();
- my @buf = ();
- my @sections = ();
-
- $invoice_data{'detail_items'} = \@detail_items;
- $invoice_data{'total_items'} = \@total_items;
- $invoice_data{'buf'} = \@buf;
- $invoice_data{'sections'} = \@sections;
-
- my $previous_section = { 'description' => 'Previous Charges',
- 'subtotal' => $other_money_char.
- sprintf('%.2f', $pr_total),
- };
-
- my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum);
- if ( $multisection ) {
- push @sections, $self->_items_sections;
- }else{
- push @sections, { 'description' => '', 'subtotal' => '' };
- }
-
- foreach my $line_item ( $self->_items_previous ) {
- my $detail = {
- ext_description => [],
- };
- $detail->{'ref'} = $line_item->{'pkgnum'};
- $detail->{'quantity'} = 1;
- $detail->{'section'} = $previous_section;
- $detail->{'description'} = &$escape_function($line_item->{'description'});
- if ( exists $line_item->{'ext_description'} ) {
- @{$detail->{'ext_description'}} = map {
- &$escape_function($_);
- } @{$line_item->{'ext_description'}};
- }
- {
- my $money = $old_latex ? '' : $money_char;
- $detail->{'amount'} = $money. $line_item->{'amount'};
- }
- $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
-
- push @detail_items, $detail;
- push @buf, [ $detail->{'description'},
- $money_char. sprintf("%10.2f", $line_item->{'amount'}),
- ];
- }
-
- if (@pr_cust_bill) {
- push @buf, ['','-----------'];
- push @buf, [ 'Total Previous Balance',
- $money_char. sprintf("%10.2f", $pr_total) ];
- push @buf, ['',''];
- }
-
- foreach my $section (@sections) {
-
- $section->{'subtotal'} = $other_money_char.
- sprintf('%.2f', $section->{'subtotal'})
- if $multisection;
-
- if ( $section->{'description'} ) {
- push @buf, ( [ &$escape_function($section->{'description'}), '' ],
- [ '', '' ],
- );
- }
-
- my %options = ();
- $options{'section'} = $section if $multisection;
-
- foreach my $line_item ( $self->_items_pkg(%options) ) {
- my $detail = {
- ext_description => [],
- };
- $detail->{'ref'} = $line_item->{'pkgnum'};
- $detail->{'quantity'} = 1;
- $detail->{'section'} = $section;
- $detail->{'description'} = &$escape_function($line_item->{'description'});
- if ( exists $line_item->{'ext_description'} ) {
- @{$detail->{'ext_description'}} = map {
- &$escape_function($_);
- } @{$line_item->{'ext_description'}};
- }
- {
- my $money = $old_latex ? '' : $money_char;
- $detail->{'amount'} = $money. $line_item->{'amount'};
- }
- $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
-
- push @detail_items, $detail;
- push @buf, ( [ $detail->{'description'},
- $money_char. sprintf("%10.2f", $line_item->{'amount'}),
- ],
- map { [ " ". $_, '' ] } @{$detail->{'ext_description'}},
- );
- }
-
- if ( $section->{'description'} ) {
- push @buf, ( ['','-----------'],
- [ $section->{'description'}. ' sub-total',
- $money_char. sprintf("%10.2f", $section->{'subtotal'})
- ],
- [ '', '' ],
- [ '', '' ],
- );
- }
-
- }
-
- if ( $multisection ) {
- unshift @sections, $previous_section;
- }
-
- my $taxtotal = 0;
- foreach my $tax ( $self->_items_tax ) {
- my $total = {};
- $total->{'total_item'} = &$escape_function($tax->{'description'});
- $taxtotal += $tax->{'amount'};
- $total->{'total_amount'} = $other_money_char. $tax->{'amount'};
- push @total_items, $total;
- push @buf,[ $total->{'total_item'},
- $money_char. sprintf("%10.2f", $total->{'total_amount'}),
- ];
-
- }
-
- if ( $taxtotal ) {
- my $total = {};
- if ( $multisection ) {
- $total->{'total_item'} = 'New charges sub-total';
- }else{
- $total->{'total_item'} = 'Sub-total';
- }
- $total->{'total_amount'} =
- $other_money_char. sprintf('%.2f', $self->charged - $taxtotal );
- unshift @total_items, $total;
- }
-
- push @buf,['','-----------'];
- push @buf,['Total New Charges',
- $money_char. sprintf("%10.2f",$self->charged) ];
- push @buf,['',''];
-
- {
- my $total = {};
- $total->{'total_item'} = &$embolden_function('Total');
- $total->{'total_amount'} =
- $total->{'total_amount'} =
- &$embolden_function(
- $other_money_char. sprintf('%.2f', $self->charged + $pr_total )
- );
- push @total_items, $total;
- push @buf,['','-----------'];
- push @buf,['Total Charges',
- $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
- push @buf,['',''];
- }
-
-
- #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
-
- # credits
- foreach my $credit ( $self->_items_credits ) {
- my $total;
- $total->{'total_item'} = &$escape_function($credit->{'description'});
- #$credittotal
- $total->{'total_amount'} = '-'. $other_money_char. $credit->{'amount'};
- push @total_items, $total;
- }
-
- # credits (again)
- foreach ( $self->cust_credited ) {
-
- #something more elaborate if $_->amount ne $_->cust_credit->credited ?
-
- my $reason = substr($_->cust_credit->reason,0,32);
- $reason .= '...' if length($reason) < length($_->cust_credit->reason);
- $reason = " ($reason) " if $reason;
- push @buf,[
- "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")". $reason,
- $money_char. sprintf("%10.2f",$_->amount)
- ];
- }
-
- # payments
- foreach my $payment ( $self->_items_payments ) {
- my $total = {};
- $total->{'total_item'} = &$escape_function($payment->{'description'});
- #$paymenttotal
- $total->{'total_amount'} = '-'. $other_money_char. $payment->{'amount'};
- push @total_items, $total;
- push @buf, [ $payment->{'description'},
- $money_char. sprintf("%10.2f", $payment->{'amount'}),
- ];
- }
-
- {
- my $total;
- $total->{'total_item'} = &$embolden_function($self->balance_due_msg);
- $total->{'total_amount'} =
- &$embolden_function(
- $other_money_char. sprintf('%.2f', $self->owed + $pr_total )
- );
- push @total_items, $total;
- push @buf,['','-----------'];
- push @buf,[$self->balance_due_msg, $money_char.
- sprintf("%10.2f", $balance_due ) ];
- }
-
- $invoice_data{'logo_file'} = $params{'logo_file'}
- if $params{'logo_file'};
-
- $invoice_lines = 0;
- my $wasfunc = 0;
- foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy
- /invoice_lines\((\d*)\)/;
- $invoice_lines += $1 || scalar(@buf);
- $wasfunc=1;
- }
- die "no invoice_lines() functions in template?"
- if ( $format eq 'template' && !$wasfunc );
-
- if ($format eq 'template') {
-
- if ( $invoice_lines ) {
- $invoice_data{'total_pages'} = int( scalar(@buf) / $invoice_lines );
- $invoice_data{'total_pages'}++
- if scalar(@buf) % $invoice_lines;
- }
-
- #setup subroutine for the template
- sub FS::cust_bill::_template::invoice_lines {
- my $lines = shift || scalar(@FS::cust_bill::_template::buf);
- map {
- scalar(@FS::cust_bill::_template::buf)
- ? shift @FS::cust_bill::_template::buf
- : [ '', '' ];
- }
- ( 1 .. $lines );
- }
-
- my $lines;
- my @collect;
- while (@buf) {
- push @collect, split("\n",
- $text_template->fill_in( HASH => \%invoice_data,
- PACKAGE => 'FS::cust_bill::_template'
- )
- );
- $FS::cust_bill::_template::page++;
- }
- map "$_\n", @collect;
- }else{
- warn "filling in template for invoice ". $self->invnum. "\n"
- if $DEBUG;
- warn join("\n", map " $_ => ". $invoice_data{$_}, keys %invoice_data). "\n"
- if $DEBUG > 1;
-
- $text_template->fill_in(HASH => \%invoice_data);
- }
-}
-
-=item print_ps [ TIME [ , TEMPLATE ] ]
-
-Returns an postscript invoice, as a scalar.
-
-TIME an optional value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-sub print_ps {
- my $self = shift;
-
- my ($file, $lfile) = $self->print_latex(@_);
- my $ps = generate_ps($file);
- unlink($lfile);
- $ps;
-
-}
-
-=item print_pdf [ TIME [ , TEMPLATE ] ]
-
-Returns an PDF invoice, as a scalar.
-
-TIME an optional value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-sub print_pdf {
- my $self = shift;
-
- my ($file, $lfile) = $self->print_latex(@_);
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- chdir($dir);
-
- #system('pdflatex', "$file.tex");
- #system('pdflatex', "$file.tex");
- #! LaTeX Error: Unknown graphics extension: .eps.
-
- my $sfile = shell_quote $file;
-
- system("pslatex $sfile.tex >/dev/null 2>&1") == 0
- or die "pslatex $file.tex failed; see $file.log for details?\n";
- system("pslatex $sfile.tex >/dev/null 2>&1") == 0
- or die "pslatex $file.tex failed; see $file.log for details?\n";
-
- #system('dvipdf', "$file.dvi", "$file.pdf" );
- system(
- "dvips -q -t letter -f $sfile.dvi ".
- "| gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$sfile.pdf ".
- " -c save pop -"
- ) == 0
- or die "dvips | gs failed: $!";
-
- open(PDF, "<$file.pdf")
- or die "can't open $file.pdf: $! (error in LaTeX template?)\n";
-
- unlink("$file.dvi", "$file.log", "$file.aux", "$file.pdf", "$file.tex");
- unlink("$lfile");
-
- my $pdf = '';
- while (<PDF>) {
- $pdf .= $_;
- }
-
- close PDF;
-
- return $pdf;
-
-}
-
-=item print_html [ TIME [ , TEMPLATE [ , CID ] ] ]
-
-Returns an HTML invoice, as a scalar.
-
-TIME an optional value used to control the printing of overdue messages. The
-default is now. It isn't the date of the invoice; that's the `_date' field.
-It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-CID is a MIME Content-ID used to create a "cid:" URL for the logo image, used
-when emailing the invoice as part of a multipart/related MIME email.
-
-=cut
-
-sub print_html {
- my( $self, $today, $template, $cid ) = @_;
-
- my %params = ( 'format' => 'html' );
- $params{'time'} = $today if $today;
- $params{'template'} = $template if $template;
- $params{'cid'} = $cid if $cid;
-
- $self->print_generic( %params );
-}
-
-# quick subroutine for print_latex
-#
-# There are ten characters that LaTeX treats as special characters, which
-# means that they do not simply typeset themselves:
-# # $ % & ~ _ ^ \ { }
-#
-# TeX ignores blanks following an escaped character; if you want a blank (as
-# in "10% of ..."), you have to "escape" the blank as well ("10\%\ of ...").
-
-sub _latex_escape {
- my $value = shift;
- $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( ( defined($2) && length($2) ) ? "\\$2" : '' )/ge;
- $value =~ s/([<>])/\$$1\$/g;
- $value;
-}
-
-#utility methods for print_*
-
-sub _translate_old_latex_format {
- warn "_translate_old_latex_format called\n"
- if $DEBUG;
-
- my @template = ();
- while ( @_ ) {
- my $line = shift;
-
- if ( $line =~ /^%%Detail\s*$/ ) {
-
- push @template, q![@--!,
- q! foreach my $_tr_line (@detail_items) {!,
- q! if ( scalar ($_tr_item->{'ext_description'} ) ) {!,
- q! $_tr_line->{'description'} .= !,
- q! "\\tabularnewline\n~~".!,
- q! join( "\\tabularnewline\n~~",!,
- q! @{$_tr_line->{'ext_description'}}!,
- q! );!,
- q! }!;
-
- while ( ( my $line_item_line = shift )
- !~ /^%%EndDetail\s*$/ ) {
- $line_item_line =~ s/'/\\'/g; # nice LTS
- $line_item_line =~ s/\\/\\\\/g; # escape quotes and backslashes
- $line_item_line =~ s/\$(\w+)/'. \$_tr_line->{$1}. '/g;
- push @template, " \$OUT .= '$line_item_line';";
- }
-
- push @template, '}',
- '--@]';
-
- } elsif ( $line =~ /^%%TotalDetails\s*$/ ) {
-
- push @template, '[@--',
- ' foreach my $_tr_line (@total_items) {';
-
- while ( ( my $total_item_line = shift )
- !~ /^%%EndTotalDetails\s*$/ ) {
- $total_item_line =~ s/'/\\'/g; # nice LTS
- $total_item_line =~ s/\\/\\\\/g; # escape quotes and backslashes
- $total_item_line =~ s/\$(\w+)/'. \$_tr_line->{$1}. '/g;
- push @template, " \$OUT .= '$total_item_line';";
- }
-
- push @template, '}',
- '--@]';
-
- } else {
- $line =~ s/\$(\w+)/[\@-- \$$1 --\@]/g;
- push @template, $line;
- }
-
- }
-
- if ($DEBUG) {
- warn "$_\n" foreach @template;
- }
-
- (@template);
-}
-
-sub terms {
- my $self = shift;
-
- #check for an invoice- specific override (eventually)
-
- #check for a customer- specific override
- return $self->cust_main->invoice_terms
- if $self->cust_main->invoice_terms;
-
- #use configured default or default default
- $conf->config('invoice_default_terms') || 'Payable upon receipt';
-}
-
-sub due_date {
- my $self = shift;
- my $duedate = '';
- if ( $self->terms =~ /^\s*Net\s*(\d+)\s*$/ ) {
- $duedate = $self->_date() + ( $1 * 86400 );
- }
- $duedate;
-}
-
-sub due_date2str {
- my $self = shift;
- $self->due_date ? time2str(shift, $self->due_date) : '';
-}
-
-sub balance_due_msg {
- my $self = shift;
- my $msg = 'Balance Due';
- return $msg unless $self->terms;
- if ( $self->due_date ) {
- $msg .= ' - Please pay by '. $self->due_date2str('%x');
- } elsif ( $self->terms ) {
- $msg .= ' - '. $self->terms;
- }
- $msg;
-}
-
-sub _items_sections {
- my $self = shift;
-
- my %s = ();
- foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
-
- if ( $cust_bill_pkg->pkgnum > 0 ) {
-
- my $desc = $cust_bill_pkg->cust_pkg->part_pkg->classname;
-
- $s{$desc} += $cust_bill_pkg->setup
- if ( $cust_bill_pkg->setup != 0 );
-
- $s{$desc} += $cust_bill_pkg->recur
- if ( $cust_bill_pkg->recur != 0 );
-
- }
-
- }
-
- map { {'description' => $_, 'subtotal' => $s{$_}} } sort keys %s;
-
-}
-
-sub _items {
- my $self = shift;
- my @display = scalar(@_)
- ? @_
- : qw( _items_previous _items_pkg );
- #: qw( _items_pkg );
- #: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments );
- my @b = ();
- foreach my $display ( @display ) {
- push @b, $self->$display(@_);
- }
- @b;
-}
-
-sub _items_previous {
- my $self = shift;
- my $cust_main = $self->cust_main;
- my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
- my @b = ();
- foreach ( @pr_cust_bill ) {
- push @b, {
- 'description' => 'Previous Balance, Invoice #'. $_->invnum.
- ' ('. time2str('%x',$_->_date). ')',
- #'pkgpart' => 'N/A',
- 'pkgnum' => 'N/A',
- 'amount' => sprintf("%.2f", $_->owed),
- };
- }
- @b;
-
- #{
- # 'description' => 'Previous Balance',
- # #'pkgpart' => 'N/A',
- # 'pkgnum' => 'N/A',
- # 'amount' => sprintf("%10.2f", $pr_total ),
- # 'ext_description' => [ map {
- # "Invoice ". $_->invnum.
- # " (". time2str("%x",$_->_date). ") ".
- # sprintf("%10.2f", $_->owed)
- # } @pr_cust_bill ],
-
- #};
-}
-
-sub _items_pkg {
- my $self = shift;
- my %options = @_;
- my $section = delete $options{'section'};
- my @cust_bill_pkg =
- grep { $_->pkgnum &&
- ( defined($section)
- ? $_->cust_pkg->part_pkg->classname eq $section->{'description'}
- : 1
- )
- } $self->cust_bill_pkg;
- $self->_items_cust_bill_pkg(\@cust_bill_pkg, %options);
-}
-
-sub _items_tax {
- my $self = shift;
- my @cust_bill_pkg = grep { ! $_->pkgnum } $self->cust_bill_pkg;
- $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
-}
-
-sub _items_cust_bill_pkg {
- my $self = shift;
- my $cust_bill_pkg = shift;
-
- my @b = ();
- foreach my $cust_bill_pkg ( @$cust_bill_pkg ) {
-
- my $desc = $cust_bill_pkg->desc;
-
- if ( $cust_bill_pkg->pkgnum > 0 ) {
-
- if ( $cust_bill_pkg->setup != 0 ) {
- my $description = $desc;
- $description .= ' Setup' if $cust_bill_pkg->recur != 0;
- my @d = $cust_bill_pkg->cust_pkg->h_labels_short($self->_date);
- push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
- push @b, {
- description => $description,
- #pkgpart => $part_pkg->pkgpart,
- pkgnum => $cust_bill_pkg->pkgnum,
- amount => sprintf("%.2f", $cust_bill_pkg->setup),
- ext_description => \@d,
- };
- }
-
- if ( $cust_bill_pkg->recur != 0 ) {
- push @b, {
- description => $desc .
- ( $conf->exists('disable_line_item_date_ranges')
- ? ''
- : " (" .time2str("%x", $cust_bill_pkg->sdate).
- " - ".time2str("%x", $cust_bill_pkg->edate).")"
- ),
- #pkgpart => $part_pkg->pkgpart,
- pkgnum => $cust_bill_pkg->pkgnum,
- amount => sprintf("%.2f", $cust_bill_pkg->recur),
- ext_description =>
- [ $cust_bill_pkg->cust_pkg->h_labels_short( $cust_bill_pkg->edate,
- $cust_bill_pkg->sdate),
- $cust_bill_pkg->details,
- ],
- };
- }
-
- } else { #pkgnum tax or one-shot line item (??)
-
- if ( $cust_bill_pkg->setup != 0 ) {
- push @b, {
- 'description' => $desc,
- 'amount' => sprintf("%.2f", $cust_bill_pkg->setup),
- };
- }
- if ( $cust_bill_pkg->recur != 0 ) {
- push @b, {
- 'description' => "$desc (".
- time2str("%x", $cust_bill_pkg->sdate). ' - '.
- time2str("%x", $cust_bill_pkg->edate). ')',
- 'amount' => sprintf("%.2f", $cust_bill_pkg->recur),
- };
- }
-
- }
-
- }
-
- @b;
-
-}
-
-sub _items_credits {
- my $self = shift;
-
- my @b;
- #credits
- foreach ( $self->cust_credited ) {
-
- #something more elaborate if $_->amount ne $_->cust_credit->credited ?
-
- my $reason = $_->cust_credit->reason;
- #my $reason = substr($_->cust_credit->reason,0,32);
- #$reason .= '...' if length($reason) < length($_->cust_credit->reason);
- $reason = " ($reason) " if $reason;
- push @b, {
- #'description' => 'Credit ref\#'. $_->crednum.
- # " (". time2str("%x",$_->cust_credit->_date) .")".
- # $reason,
- 'description' => 'Credit applied '.
- time2str("%x",$_->cust_credit->_date). $reason,
- 'amount' => sprintf("%.2f",$_->amount),
- };
- }
- #foreach ( @cr_cust_credit ) {
- # push @buf,[
- # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
- # $money_char. sprintf("%10.2f",$_->credited)
- # ];
- #}
-
- @b;
-
-}
-
-sub _items_payments {
- my $self = shift;
-
- my @b;
- #get & print payments
- foreach ( $self->cust_bill_pay ) {
-
- #something more elaborate if $_->amount ne ->cust_pay->paid ?
-
- push @b, {
- 'description' => "Payment received ".
- time2str("%x",$_->cust_pay->_date ),
- 'amount' => sprintf("%.2f", $_->amount )
- };
- }
-
- @b;
-
-}
-
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item reprint
-
-=cut
-
-sub process_reprint {
- process_re_X('print', @_);
-}
-
-=item reemail
-
-=cut
-
-sub process_reemail {
- process_re_X('email', @_);
-}
-
-=item refax
-
-=cut
-
-sub process_refax {
- process_re_X('fax', @_);
-}
-
-use Storable qw(thaw);
-use Data::Dumper;
-use MIME::Base64;
-sub process_re_X {
- my( $method, $job ) = ( shift, shift );
- warn "$me process_re_X $method for job $job\n" if $DEBUG;
-
- my $param = thaw(decode_base64(shift));
- warn Dumper($param) if $DEBUG;
-
- re_X(
- $method,
- $job,
- %$param,
- );
-
-}
-
-sub re_X {
- my($method, $job, %param ) = @_;
- if ( $DEBUG ) {
- warn "re_X $method for job $job with param:\n".
- join( '', map { " $_ => ". $param{$_}. "\n" } keys %param );
- }
-
- #some false laziness w/search/cust_bill.html
- my $distinct = '';
- my $orderby = 'ORDER BY cust_bill._date';
-
- my $extra_sql = ' WHERE '. FS::cust_bill->search_sql(\%param);
-
- my $addl_from = 'LEFT JOIN cust_main USING ( custnum )';
-
- my @cust_bill = qsearch( {
- #'select' => "cust_bill.*",
- 'table' => 'cust_bill',
- 'addl_from' => $addl_from,
- 'hashref' => {},
- 'extra_sql' => $extra_sql,
- 'order_by' => $orderby,
- 'debug' => 1,
- } );
-
- warn " $me re_X $method: ". scalar(@cust_bill). " invoices found\n"
- if $DEBUG;
-
- my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
- foreach my $cust_bill ( @cust_bill ) {
- $cust_bill->$method();
-
- if ( $job ) { #progressbar foo
- $num++;
- if ( time - $min_sec > $last ) {
- my $error = $job->update_statustext(
- int( 100 * $num / scalar(@cust_bill) )
- );
- die $error if $error;
- $last = time;
- }
- }
-
- }
-
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item owed_sql
-
-Returns an SQL fragment to retreive the amount owed (charged minus credited and paid).
-
-=cut
-
-sub owed_sql {
- my $class = shift;
- 'charged - '. $class->paid_sql. ' - '. $class->credited_sql;
-}
-
-=item net_sql
-
-Returns an SQL fragment to retreive the net amount (charged minus credited).
-
-=cut
-
-sub net_sql {
- my $class = shift;
- 'charged - '. $class->credited_sql;
-}
-
-=item paid_sql
-
-Returns an SQL fragment to retreive the amount paid against this invoice.
-
-=cut
-
-sub paid_sql {
- #my $class = shift;
- "( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
- WHERE cust_bill.invnum = cust_bill_pay.invnum )";
-}
-
-=item credited_sql
-
-Returns an SQL fragment to retreive the amount credited against this invoice.
-
-=cut
-
-sub credited_sql {
- #my $class = shift;
- "( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
- WHERE cust_bill.invnum = cust_credit_bill.invnum )";
-}
-
-=item search_sql HASHREF
-
-Class method which returns an SQL WHERE fragment to search for parameters
-specified in HASHREF. Valid parameters are
-
-=over 4
-
-=item begin
-
-Epoch date (UNIX timestamp) setting a lower bound for _date values
-
-=item end
-
-Epoch date (UNIX timestamp) setting an upper bound for _date values
-
-=item invnum_min
-
-=item invnum_max
-
-=item agentnum
-
-=item owed
-
-=item net
-
-=item days
-
-=item newest_percust
-
-=back
-
-Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
-
-=cut
-
-sub search_sql {
- my($class, $param) = @_;
- if ( $DEBUG ) {
- warn "$me search_sql called with params: \n".
- join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n";
- }
-
- my @search = ();
-
- if ( $param->{'begin'} =~ /^(\d+)$/ ) {
- push @search, "cust_bill._date >= $1";
- }
- if ( $param->{'end'} =~ /^(\d+)$/ ) {
- push @search, "cust_bill._date < $1";
- }
- if ( $param->{'invnum_min'} =~ /^(\d+)$/ ) {
- push @search, "cust_bill.invnum >= $1";
- }
- if ( $param->{'invnum_max'} =~ /^(\d+)$/ ) {
- push @search, "cust_bill.invnum <= $1";
- }
- if ( $param->{'agentnum'} =~ /^(\d+)$/ ) {
- push @search, "cust_main.agentnum = $1";
- }
-
- push @search, '0 != '. FS::cust_bill->owed_sql
- if $param->{'open'};
-
- push @search, '0 != '. FS::cust_bill->net_sql
- if $param->{'net'};
-
- push @search, "cust_bill._date < ". (time-86400*$param->{'days'})
- if $param->{'days'};
-
- if ( $param->{'newest_percust'} ) {
-
- #$distinct = 'DISTINCT ON ( cust_bill.custnum )';
- #$orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC';
-
- my @newest_where = map { my $x = $_;
- $x =~ s/\bcust_bill\./newest_cust_bill./g;
- $x;
- }
- grep ! /^cust_main./, @search;
- my $newest_where = scalar(@newest_where)
- ? ' AND '. join(' AND ', @newest_where)
- : '';
-
-
- push @search, "cust_bill._date = (
- SELECT(MAX(newest_cust_bill._date)) FROM cust_bill AS newest_cust_bill
- WHERE newest_cust_bill.custnum = cust_bill.custnum
- $newest_where
- )";
-
- }
-
- my $curuser = $FS::CurrentUser::CurrentUser;
- if ( $curuser->username eq 'fs_queue'
- && $param->{'CurrentUser'} =~ /^(\w+)$/ ) {
- my $username = $1;
- my $newuser = qsearchs('access_user', {
- 'username' => $username,
- 'disabled' => '',
- } );
- if ( $newuser ) {
- $curuser = $newuser;
- } else {
- warn "$me WARNING: (fs_queue) can't find CurrentUser $username\n";
- }
- }
-
- push @search, $curuser->agentnums_sql;
-
- join(' AND ', @search );
-
-}
-
-=back
-
-=head1 BUGS
-
-The delete method.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill_pay>, L<FS::cust_pay>,
-L<FS::cust_bill_pkg>, L<FS::cust_bill_credit>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_bill_ApplicationCommon.pm b/FS/FS/cust_bill_ApplicationCommon.pm
deleted file mode 100644
index 24274e7..0000000
--- a/FS/FS/cust_bill_ApplicationCommon.pm
+++ /dev/null
@@ -1,390 +0,0 @@
-package FS::cust_bill_ApplicationCommon;
-
-use strict;
-use vars qw( @ISA $DEBUG $me );
-use List::Util qw(min);
-use FS::Schema qw( dbdef );
-use FS::Record qw( qsearch qsearchs dbh );
-
-@ISA = qw( FS::Record );
-
-$DEBUG = 0;
-$me = '[FS::cust_bill_ApplicationCommon]';
-
-=head1 NAME
-
-FS::cust_bill_ApplicationCommon - Base class for bill application classes
-
-=head1 SYNOPSIS
-
-use FS::cust_bill_ApplicationCommon;
-
-@ISA = qw( FS::cust_bill_ApplicationCommon );
-
-sub _app_source_name { 'payment'; }
-sub _app_source_table { 'cust_pay'; }
-sub _app_lineitem_breakdown_table { 'cust_bill_pay_pkg'; }
-
-=head1 DESCRIPTION
-
-FS::cust_bill_ApplicationCommon is intended as a base class for classes which
-represent application of things to invoices, currently payments
-(see L<FS::cust_bill_pay>) or credits (see L<FS::cust_credit_bill>).
-
-=head1 METHODS
-
-=item insert
-
-=cut
-
-sub insert {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::insert(@_)
- || $self->apply_to_lineitems;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-=cut
-
-sub delete {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $app ( $self->lineitem_applications ) {
- my $error = $app->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $error = $self->SUPER::delete(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item apply_to_lineitems
-
-Auto-applies this invoice application to specific line items, if possible.
-
-=cut
-
-sub apply_to_lineitems {
- my $self = shift;
-
- my @apply = ();
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my @open = $self->cust_bill->open_cust_bill_pkg; #FOR UPDATE...?
- warn "$me ". scalar(@open). " open line items for invoice ".
- $self->cust_bill->invnum. ": ". join(', ', @open). "\n"
- if $DEBUG;
- my $total = 0;
- $total += $_->setup + $_->recur foreach @open;
- $total = sprintf('%.2f', $total);
-
- if ( $self->amount > $total ) {
- $dbh->rollback if $oldAutoCommit;
- return "Can't apply a ". $self->_app_source_name. ' of $'. $self->amount.
- " greater than the remaining owed on line items (\$$total)";
- }
-
- #easy cases:
- # - one lineitem (a simple special case of:)
- # - amount is for whole invoice (well, all of remaining lineitem links)
- if ( $self->amount == $total ) {
-
- warn "$me application amount covers remaining balance of invoice in full;".
- "applying to those lineitems\n"
- if $DEBUG;
-
- #@apply = map { [ $_, $_->amount ]; } @open;
- @apply = map { [ $_, $_->setup || $_->recur ]; } @open;
-
- } else {
-
- #slightly magic case:
- # - amount exactly and uniquely matches a single open lineitem
- # (you must be trying to pay or credit that item, then)
-
- my @same = grep { $_->setup == $self->amount
- || $_->recur == $self->amount
- }
- @open;
- if ( scalar(@same) == 1 ) {
- warn "$me application amount exactly and uniquely matches one lineitem;".
- " applying to that lineitem\n"
- if $DEBUG;
- @apply = map { [ $_, $self->amount ]; } @same
- }
-
- }
-
- unless ( @apply ) {
-
- warn "$me applying amount based on package weights\n"
- if $DEBUG;
-
- #and the rest:
- # - apply based on weights...
-
- my $weight_col = $self->_app_part_pkg_weight_column;
- my @openweight = map {
- my $open = $_;
- my $cust_pkg = $open->cust_pkg;
- my $weight =
- $cust_pkg
- ? ( $cust_pkg->part_pkg->$weight_col() || 0 )
- : 0; #default or per-tax weight?
- [ $open, $weight ]
- }
- @open;
-
- my %saw = ();
- my @weights = sort { $b <=> $a } # highest weight first
- grep { ! $saw{$_}++ } # want a list of unique weights
- map { $_->[1] }
- @openweight;
-
- my $remaining_amount = $self->amount;
- foreach my $weight ( @weights ) {
-
- #i hate it when my schwartz gets tangled
- my @items = map { $_->[0] } grep { $weight == $_->[1] } @openweight;
-
- my $itemtotal = 0;
- foreach my $item (@items) { $itemtotal += $item->setup || $item->recur; }
- my $applytotal = min( $itemtotal, $remaining_amount );
- $remaining_amount -= $applytotal;
-
- warn "$me applying $applytotal ($remaining_amount remaining)".
- " to ". scalar(@items). " lineitems with weight $weight\n"
- if $DEBUG;
-
- #if some items are less than applytotal/num_items, then apply then in full
- my $lessflag;
- do {
- $lessflag = 0;
-
- #no, not sprintf("%.2f",
- # we want this rounded DOWN for purposes of checking for line items
- # less than it, we don't want .66666 becoming .67 and causing this
- # to trigger when it shouldn't
- my $applyeach = int( 100 * $applytotal / scalar(@items) ) / 100;
-
- my @newitems = ();
- foreach my $item ( @items ) {
- my $itemamount = $item->setup || $item->recur;
- if ( $itemamount < $applyeach ) {
- warn "$me applying full $itemamount".
- " to small line item (cust_bill_pkg ". $item->billpkgnum. ")\n"
- if $DEBUG;
- push @apply, [ $item, $itemamount ];
- $applytotal -= $itemamount;
- $lessflag=1;
- } else {
- push @newitems, $item;
- }
- }
- @items = @newitems;
-
- } while ( $lessflag );
-
- #and now that we've fallen out of the loop, distribute the rest equally...
-
- # should cust_bill_pay_pkg and cust_credit_bill_pkg amount columns
- # become real instead of numeric(10,2) ??? no..
- my $applyeach = sprintf("%.2f", $applytotal / scalar(@items) );
-
- my @equi_apply = map { [ $_, $applyeach ] } @items;
-
- # or should we futz with pennies instead? yes, bah!
- my $diff =
- sprintf('%.0f', 100 * ( $applytotal - $applyeach * scalar(@items) ) );
- $diff = 0 if $diff eq '-0'; #yay ieee fp
- if ( abs($diff) > scalar(@items) ) {
- #we must have done something really wrong, the difference is more than
- #a penny an item
- $dbh->rollback if $oldAutoCommit;
- return 'Error distributing pennies applying '. $self->_app_source_name.
- " - can't distribute difference of $diff pennies".
- ' among '. scalar(@items). ' line items';
- }
-
- warn "$me futzing with $diff pennies difference\n"
- if $DEBUG && $diff;
-
- my $futz = 0;
- while ( $diff != 0 && $futz < scalar(@equi_apply) ) {
- if ( $diff > 0 ) {
- $equi_apply[$futz++]->[1] += .01;
- $diff -= 1;
- } elsif ( $diff < 0 ) {
- $equi_apply[$futz++]->[1] -= .01;
- $diff += 1;
- } else {
- die "guru exception #5 (in fortran tongue the answer)";
- }
- }
-
- if ( sprintf('%.0f', $diff ) ) {
- $dbh->rollback if $oldAutoCommit;
- return "couldn't futz with pennies enough: still $diff left";
- }
-
- if ( $DEBUG ) {
- warn "$me applying ". $_->[1].
- " to line item (cust_bill_pkg ". $_->[0]->billpkgnum. ")\n"
- foreach @equi_apply;
- }
-
-
- push @apply, @equi_apply;
-
- #$remaining_amount -= $applytotal;
- last unless $remaining_amount;
-
- }
-
- }
-
- # do the applicaiton(s)
- my $table = $self->lineitem_breakdown_table;
- my $source_key = dbdef->table($self->table)->primary_key;
- my $applied = 0;
- foreach my $apply ( @apply ) {
- my ( $cust_bill_pkg, $amount ) = @$apply;
- $applied += $amount;
- my $application = "FS::$table"->new( {
- $source_key => $self->$source_key(),
- 'billpkgnum' => $cust_bill_pkg->billpkgnum,
- 'amount' => sprintf('%.2f', $amount),
- 'setuprecur' => ( $cust_bill_pkg->setup > 0 ? 'setup' : 'recur' ),
- 'sdate' => $cust_bill_pkg->sdate,
- 'edate' => $cust_bill_pkg->edate,
- });
- my $error = $application->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- #everything should always be applied to line items in full now... sanity check
- $applied = sprintf('%.2f', $applied);
- unless ( $applied == $self->amount ) {
- $dbh->rollback if $oldAutoCommit;
- return 'Error applying '. $self->_app_source_name. ' of $'. $self->amount.
- ' to line items - only $'. $applied. ' was applied.';
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item lineitem_applications
-
-Returns all the specific line item applications for this invoice application.
-
-=cut
-
-sub lineitem_applications {
- my $self = shift;
- my $primary_key = dbdef->table($self->table)->primary_key;
- qsearch({
- 'table' => $self->lineitem_breakdown_table,
- 'hashref' => { $primary_key => $self->$primary_key() },
- });
-
-}
-
-=item cust_bill
-
-Returns the invoice (see L<FS::cust_bill>)
-
-=cut
-
-sub cust_bill {
- my $self = shift;
- qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
-}
-
-=item lineitem_breakdown_table
-
-=cut
-
-sub lineitem_breakdown_table {
- my $self = shift;
- $self->_load_table($self->_app_lineitem_breakdown_table);
-}
-
-sub _load_table {
- my( $self, $table ) = @_;
- eval "use FS::$table";
- die $@ if $@;
- $table;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::cust_bill_pay> and L<FS::cust_bill_pay_pkg>,
-L<FS::cust_credit_bill> and L<FS::cust_credit_bill_pkg>
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_bill_event.pm b/FS/FS/cust_bill_event.pm
deleted file mode 100644
index 7c2ad37..0000000
--- a/FS/FS/cust_bill_event.pm
+++ /dev/null
@@ -1,380 +0,0 @@
-package FS::cust_bill_event;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use FS::Record qw( qsearch qsearchs );
-use FS::cust_main_Mixin;
-use FS::cust_bill;
-use FS::part_bill_event;
-
-@ISA = qw(FS::cust_main_Mixin FS::Record);
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::cust_bill_event - Object methods for cust_bill_event records
-
-=head1 SYNOPSIS
-
- use FS::cust_bill_event;
-
- $record = new FS::cust_bill_event \%hash;
- $record = new FS::cust_bill_event { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_bill_event object represents an complete invoice event.
-FS::cust_bill_event inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item eventnum
-
-Primary key
-
-=item invnum
-
-Invoice (see L<FS::cust_bill>)
-
-=item eventpart
-
-Event definition (see L<FS::part_bill_event>)
-
-=item _date
-
-Specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item status
-
-Event status: B<done> or B<failed>
-
-=item statustext
-
-Additional status detail (i.e. error message)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new completed invoice event. To add the compelted invoice event to
-the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_bill_event'; }
-
-sub cust_linked { $_[0]->cust_main_custnum; }
-sub cust_unlinked_msg {
- my $self = shift;
- "WARNING: can't find cust_main.custnum ". $self->custnum.
- ' (cust_bill.invnum '. $self->invnum. ')';
-}
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid completed invoice event. If
-there is an error, returns the error, otherwise returns false. Called by the
-insert and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error = $self->ut_numbern('eventnum')
- || $self->ut_number('invnum')
- || $self->ut_number('eventpart')
- || $self->ut_number('_date')
- || $self->ut_enum('status', [qw( done failed )])
- || $self->ut_anything('statustext')
- ;
-
- return "Unknown eventpart ". $self->eventpart
- unless my $part_bill_event =
- qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } );
-
- return "Unknown invnum ". $self->invnum
- unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
-
- $self->SUPER::check;
-}
-
-=item part_bill_event
-
-Returns the invoice event definition (see L<FS::part_bill_event>) for this
-completed invoice event.
-
-=cut
-
-sub part_bill_event {
- my $self = shift;
- qsearchs( 'part_bill_event', { 'eventpart' => $self->eventpart } );
-}
-
-=item cust_bill
-
-Returns the invoice (see L<FS::cust_bill>) for this completed invoice event.
-
-=cut
-
-sub cust_bill {
- my $self = shift;
- qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
-}
-
-=item retry
-
-Changes the status of this event from B<done> to B<failed>, allowing it to be
-retried.
-
-=cut
-
-sub retry {
- my $self = shift;
- return '' unless $self->status eq 'done';
- my $old = ref($self)->new( { $self->hash } );
- $self->status('failed');
- $self->replace($old);
-}
-
-=item retryable
-
-Changes the statustext of this event to B<retriable>, rendering it
-retriable (should retry be called).
-
-=cut
-
-sub retriable {
- my $self = shift;
- return '' unless $self->status eq 'done';
- my $old = ref($self)->new( { $self->hash } );
- $self->statustext('retriable');
- $self->replace($old);
-}
-
-=item search_sql HASHREF
-
-Class method which returns an SQL WHERE fragment to search for parameters
-specified in HASHREF. Valid parameters are
-
-=over 4
-
-=item agentnum
-
-=item beginning
-
-An epoch date setting a lower bound for _date values
-
-=item ending
-
-An epoch date setting a upper bound for _date values
-
-=item failed
-
-Limits the search to failed events if true
-
-=item payby
-
-Requires that the search be JOIN'd to part_bill_event # Bug?
-
-=item invnum
-
-=item currentuser
-
-Specifies the user for agent virtualization
-
-=back
-
-=cut
-
-sub search_sql {
- my ($class, $params) = @_;
- my @search = ();
-
- push @search, "agentnum = ". $params->{agentnum} if $params->{agentnum};
-
- push @search, "cust_bill_event._date >= ". $params->{beginning}
- if $params->{beginning};
- push @search, "cust_bill_event._date <= ". $params->{ending}
- if $params->{ending};
-
- push @search, "statustext != ''",
- "statustext IS NOT NULL",
- "statustext != 'N/A'"
- if $params->{failed};
-
- push @search, "part_bill_event.payby = '". $params->{payby}. "'"
- if $params->{payby};
-
- push @search, "cust_bill_event.invnum = '". $params->{invnum}. "'"
- if $params->{invnum};
-
- my $currentuser = $params->{currentuser} || $params->{CurrentUser};
- if ($currentuser) {
- my $access_user = qsearchs('access_user', { username => $currentuser });
- if ($access_user) {
- push @search, $access_user->agentnums_sql;
- }else{
- push @search, "1=0";
- }
- }else{
- push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
- }
-
- join(' AND ', @search );
-
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item reprint
-
-=cut
-
-sub process_reprint {
- process_re_X('print', @_);
-}
-
-=item reemail
-
-=cut
-
-sub process_reemail {
- process_re_X('email', @_);
-}
-
-=item refax
-
-=cut
-
-sub process_refax {
- process_re_X('fax', @_);
-}
-
-use Storable qw(thaw);
-use Data::Dumper;
-use MIME::Base64;
-sub process_re_X {
- my( $method, $job ) = ( shift, shift );
-
- my $param = thaw(decode_base64(shift));
- warn Dumper($param) if $DEBUG;
-
- re_X(
- $method,
- $param,
- $job,
- );
-
-}
-
-sub re_X {
- my($method, $param, $job) = @_;
-
- my $where = FS::cust_bill_event->search_sql($param);
- $where = " WHERE plan LIKE 'send%'". ( $where ? " AND $where" : "" );
-
- my $from = 'LEFT JOIN part_bill_event USING ( eventpart )'.
- 'LEFT JOIN cust_bill USING ( invnum )'.
- 'LEFT JOIN cust_main USING ( custnum )';
-
- my @cust_bill_event = qsearch( 'cust_bill_event', {}, '', $where, '', $from );
-
- my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
- foreach my $cust_bill_event ( @cust_bill_event ) {
-
- $cust_bill_event->cust_bill->$method(
- $cust_bill_event->part_bill_event->templatename
- );
-
- if ( $job ) { #progressbar foo
- $num++;
- if ( time - $min_sec > $last ) {
- my $error = $job->update_statustext(
- int( 100 * $num / scalar(@cust_bill_event) )
- );
- die $error if $error;
- $last = time;
- }
- }
-
- }
-
- #this doesn't work, but it would be nice
- #if ( $job ) { #progressbar foo
- # my $error = $job->update_statustext(
- # scalar(@cust_bill_event). " invoices re-${method}ed"
- # );
- # die $error if $error;
- #}
-
-}
-
-=back
-
-=head1 BUGS
-
-Far too early in the morning.
-
-=head1 SEE ALSO
-
-L<FS::part_bill_event>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_bill_pay.pm b/FS/FS/cust_bill_pay.pm
deleted file mode 100644
index 74a8bcd..0000000
--- a/FS/FS/cust_bill_pay.pm
+++ /dev/null
@@ -1,164 +0,0 @@
-package FS::cust_bill_pay;
-
-use strict;
-use vars qw( @ISA $conf );
-use FS::Record qw( qsearchs );
-use FS::cust_bill_ApplicationCommon;
-use FS::cust_bill;
-use FS::cust_pay;
-
-@ISA = qw( FS::cust_bill_ApplicationCommon );
-
-#ask FS::UID to run this stuff for us later
-FS::UID->install_callback( sub {
- $conf = new FS::Conf;
-} );
-
-=head1 NAME
-
-FS::cust_bill_pay - Object methods for cust_bill_pay records
-
-=head1 SYNOPSIS
-
- use FS::cust_bill_pay;
-
- $record = new FS::cust_bill_pay \%hash;
- $record = new FS::cust_bill_pay { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_bill_pay object represents the application of a payment to a
-specific invoice. FS::cust_bill_pay inherits from
-FS::cust_bill_ApplicationCommon and FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item billpaynum - primary key (assigned automatically)
-
-=item invnum - Invoice (see L<FS::cust_bill>)
-
-=item paynum - Payment (see L<FS::cust_pay>)
-
-=item amount - Amount of the payment to apply to the specific invoice.
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-=cut
-
-sub table { 'cust_bill_pay'; }
-
-sub _app_source_name { 'payment'; }
-sub _app_source_table { 'cust_pay'; }
-sub _app_lineitem_breakdown_table { 'cust_bill_pay_pkg'; }
-sub _app_part_pkg_weight_column { 'pay_weight'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this payment application, unless the closed flag for the parent payment
-(see L<FS::cust_pay>) is set.
-
-=cut
-
-sub delete {
- my $self = shift;
- return "Can't delete application for closed payment"
- if $self->cust_pay->closed =~ /^Y/i;
- return "Can't delete application for closed invoice"
- if $self->cust_bill->closed =~ /^Y/i;
- $self->SUPER::delete(@_);
-}
-
-=item replace OLD_RECORD
-
-Currently unimplemented (accounting reasons).
-
-=cut
-
-sub replace {
- return "Can't modify application of payment!";
-}
-
-=item check
-
-Checks all fields to make sure this is a valid payment application. If there
-is an error, returns the error, otherwise returns false. Called by the insert
-method.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('billpaynum')
- || $self->ut_foreign_key('paynum', 'cust_pay', 'paynum' )
- || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum' )
- || $self->ut_numbern('_date')
- || $self->ut_money('amount')
- ;
- return $error if $error;
-
- return "amount must be > 0" if $self->amount <= 0;
-
- $self->_date(time) unless $self->_date;
-
- return "Cannot apply more than remaining value of invoice"
- unless $self->amount <= $self->cust_bill->owed;
-
- return "Cannot apply more than remaining value of payment"
- unless $self->amount <= $self->cust_pay->unapplied;
-
- $self->SUPER::check;
-}
-
-=item cust_pay
-
-Returns the payment (see L<FS::cust_pay>)
-
-=cut
-
-sub cust_pay {
- my $self = shift;
- qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
-}
-
-=back
-
-=head1 BUGS
-
-Delete and replace methods.
-
-=head1 SEE ALSO
-
-L<FS::cust_pay>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_bill_pay_batch.pm b/FS/FS/cust_bill_pay_batch.pm
deleted file mode 100644
index 30fb744..0000000
--- a/FS/FS/cust_bill_pay_batch.pm
+++ /dev/null
@@ -1,120 +0,0 @@
-package FS::cust_bill_pay_batch;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cust_bill_pay_batch - Object methods for cust_bill_pay_batch records
-
-=head1 SYNOPSIS
-
- use FS::cust_bill_pay_batch;
-
- $record = new FS::cust_bill_pay_batch \%hash;
- $record = new FS::cust_bill_pay_batch { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_bill_pay_batch object represents a relationship between a
-customer's bill and a batch. FS::cust_bill_pay_batch inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item billpaynum - primary key
-
-=item invnum - customer's bill (invoice)
-
-=item paybatchnum - entry in cust_pay_batch table
-
-=item amount -
-
-=item _date -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'cust_bill_pay_batch'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-=item check
-
-Checks all fields to make sure this is a valid example. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('billpaynum')
- || $self->ut_number('invnum')
- || $self->ut_number('paybatchnum')
- || $self->ut_money('amount')
- || $self->ut_numbern('_date')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-Just hangs there.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_bill_pay_pkg.pm b/FS/FS/cust_bill_pay_pkg.pm
deleted file mode 100644
index cdbace9..0000000
--- a/FS/FS/cust_bill_pay_pkg.pm
+++ /dev/null
@@ -1,141 +0,0 @@
-package FS::cust_bill_pay_pkg;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cust_bill_pay_pkg - Object methods for cust_bill_pay_pkg records
-
-=head1 SYNOPSIS
-
- use FS::cust_bill_pay_pkg;
-
- $record = new FS::cust_bill_pay_pkg \%hash;
- $record = new FS::cust_bill_pay_pkg { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_bill_pay_pkg object represents application of a payment (see
-L<FS::cust_bill_pay>) to a specific line item within an invoice (see
-L<FS::cust_bill_pkg>). FS::cust_bill_pay_pkg inherits from FS::Record. The
-following fields are currently supported:
-
-=over 4
-
-=item billpaypkgnum - primary key
-
-=item billpaynum - Payment application to the overall invoice (see L<FS::cust_bill_pay>)
-
-=item billpkgnum - Line item to which payment is applied (see L<FS::cust_bill_pkg>)
-
-=item amount - Amount of the payment applied to this line item.
-
-=item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item.
-
-=item sdate - starting date of recurring fee
-
-=item edate - ending date of recurring fee
-
-=back
-
-sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">. Also
-see L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_bill_pay_pkg'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid payment application. If there
-is an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('billpaypkgnum')
- || $self->ut_foreign_key('billpaynum', 'cust_bill_pay', 'billpaynum' )
- || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
- || $self->ut_money('amount')
- || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
- || $self->ut_numbern('sdate')
- || $self->ut_numbern('edate')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
-setup and recur fields. It should be removed once that's fixed.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
deleted file mode 100644
index 9fddf6b..0000000
--- a/FS/FS/cust_bill_pkg.pm
+++ /dev/null
@@ -1,320 +0,0 @@
-package FS::cust_bill_pkg;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs dbdef dbh );
-use FS::cust_main_Mixin;
-use FS::cust_pkg;
-use FS::cust_bill;
-use FS::cust_bill_pkg_detail;
-use FS::cust_bill_pay_pkg;
-use FS::cust_credit_bill_pkg;
-
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
-=head1 NAME
-
-FS::cust_bill_pkg - Object methods for cust_bill_pkg records
-
-=head1 SYNOPSIS
-
- use FS::cust_bill_pkg;
-
- $record = new FS::cust_bill_pkg \%hash;
- $record = new FS::cust_bill_pkg { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_bill_pkg object represents an invoice line item.
-FS::cust_bill_pkg inherits from FS::Record. The following fields are currently
-supported:
-
-=over 4
-
-=item billpkgnum - primary key
-
-=item invnum - invoice (see L<FS::cust_bill>)
-
-=item pkgnum - package (see L<FS::cust_pkg>) or 0 for the special virtual sales tax package, or -1 for the virtual line item (itemdesc is used for the line)
-
-=item setup - setup fee
-
-=item recur - recurring fee
-
-=item sdate - starting date of recurring fee
-
-=item edate - ending date of recurring fee
-
-=item itemdesc - Line item description (currentlty used only when pkgnum is 0 or -1)
-
-=back
-
-sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">. Also
-see L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new line item. To add the line item to the database, see
-L<"insert">. Line items are normally created by calling the bill method of a
-customer object (see L<FS::cust_main>).
-
-=cut
-
-sub table { 'cust_bill_pkg'; }
-
-=item insert
-
-Adds this line item to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub insert {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- unless ( defined dbdef->table('cust_bill_pkg_detail') && $self->get('details') ) {
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- return '';
- }
-
- foreach my $detail ( @{$self->get('details')} ) {
- my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
- 'pkgnum' => $self->pkgnum,
- 'invnum' => $self->invnum,
- 'detail' => $detail,
- };
- $error = $cust_bill_pkg_detail->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item delete
-
-Currently unimplemented. I don't remove line items because there would then be
-no record the items ever existed (which is bad, no?)
-
-=cut
-
-sub delete {
- return "Can't delete cust_bill_pkg records!";
-}
-
-=item replace OLD_RECORD
-
-Currently unimplemented. This would be even more of an accounting nightmare
-than deleteing the items. Just don't do it.
-
-=cut
-
-sub replace {
- return "Can't modify cust_bill_pkg records!";
-}
-
-=item check
-
-Checks all fields to make sure this is a valid line item. If there is an
-error, returns the error, otherwise returns false. Called by the insert
-method.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('billpkgnum')
- || $self->ut_snumber('pkgnum')
- || $self->ut_number('invnum')
- || $self->ut_money('setup')
- || $self->ut_money('recur')
- || $self->ut_numbern('sdate')
- || $self->ut_numbern('edate')
- || $self->ut_textn('itemdesc')
- ;
- return $error if $error;
-
- #if ( $self->pkgnum != 0 ) { #allow unchecked pkgnum 0 for tax! (add to part_pkg?)
- if ( $self->pkgnum > 0 ) { #allow -1 for non-pkg line items and 0 for tax (add to part_pkg?)
- return "Unknown pkgnum ". $self->pkgnum
- unless qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
- }
-
- return "Unknown invnum"
- unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
-
- $self->SUPER::check;
-}
-
-=item cust_pkg
-
-Returns the package (see L<FS::cust_pkg>) for this invoice line item.
-
-=cut
-
-sub cust_pkg {
- my $self = shift;
- qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
-}
-
-=item cust_bill
-
-Returns the invoice (see L<FS::cust_bill>) for this invoice line item.
-
-=cut
-
-sub cust_bill {
- my $self = shift;
- qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
-}
-
-=item details
-
-Returns an array of detail information for the invoice line item.
-
-=cut
-
-sub details {
- my $self = shift;
- return () unless defined dbdef->table('cust_bill_pkg_detail');
- map { $_->detail }
- qsearch ( 'cust_bill_pkg_detail', { 'pkgnum' => $self->pkgnum,
- 'invnum' => $self->invnum, } );
- #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum });
-}
-
-=item desc
-
-Returns a description for this line item. For typical line items, this is the
-I<pkg> field of the corresponding B<FS::part_pkg> object (see L<FS::part_pkg>).
-For one-shot line items and named taxes, it is the I<itemdesc> field of this
-line item, and for generic taxes, simply returns "Tax".
-
-=cut
-
-sub desc {
- my $self = shift;
-
- if ( $self->pkgnum > 0 ) {
- $self->cust_pkg->part_pkg->pkg;
- } else {
- $self->itemdesc || 'Tax';
- }
-}
-
-=item owed_setup
-
-Returns the amount owed (still outstanding) on this line item's setup fee,
-which is the amount of the line item minus all payment applications (see
-L<FS::cust_bill_pay_pkg> and credit applications (see
-L<FS::cust_credit_bill_pkg>).
-
-=cut
-
-sub owed_setup {
- my $self = shift;
- $self->owed('setup', @_);
-}
-
-=item owed_recur
-
-Returns the amount owed (still outstanding) on this line item's recurring fee,
-which is the amount of the line item minus all payment applications (see
-L<FS::cust_bill_pay_pkg> and credit applications (see
-L<FS::cust_credit_bill_pkg>).
-
-=cut
-
-sub owed_recur {
- my $self = shift;
- $self->owed('recur', @_);
-}
-
-# modeled after cust_bill::owed...
-sub owed {
- my( $self, $field ) = @_;
- my $balance = $self->$field();
- $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg($field) );
- $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg($field) );
- $balance = sprintf( '%.2f', $balance );
- $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
- $balance;
-}
-
-sub cust_bill_pay_pkg {
- my( $self, $field ) = @_;
- qsearch( 'cust_bill_pay_pkg', { 'billpkgnum' => $self->billpkgnum,
- 'setuprecur' => $field,
- }
- );
-}
-
-sub cust_credit_bill_pkg {
- my( $self, $field ) = @_;
- qsearch( 'cust_credit_bill_pkg', { 'billpkgnum' => $self->billpkgnum,
- 'setuprecur' => $field,
- }
- );
-}
-
-=back
-
-=head1 BUGS
-
-setup and recur shouldn't be separate fields. There should be one "amount"
-field and a flag to tell you if it is a setup/one-time fee or a recurring fee.
-
-A line item with both should really be two separate records (preserving
-sdate and edate for setup fees for recurring packages - that information may
-be valuable later). Invoice generation (cust_main::bill), invoice printing
-(cust_bill), tax reports (report_tax.cgi) and line item reports
-(cust_bill_pkg.cgi) would need to be updated.
-
-owed_setup and owed_recur could then be repaced by just owed, and
-cust_bill::open_cust_bill_pkg and
-cust_bill_ApplicationCommon::apply_to_lineitems could be simplified.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_bill>, L<FS::cust_pkg>, L<FS::cust_main>, schema.html
-from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_bill_pkg_detail.pm b/FS/FS/cust_bill_pkg_detail.pm
deleted file mode 100644
index 4156816..0000000
--- a/FS/FS/cust_bill_pkg_detail.pm
+++ /dev/null
@@ -1,124 +0,0 @@
-package FS::cust_bill_pkg_detail;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cust_bill_pkg_detail - Object methods for cust_bill_pkg_detail records
-
-=head1 SYNOPSIS
-
- use FS::cust_bill_pkg_detail;
-
- $record = new FS::cust_bill_pkg_detail \%hash;
- $record = new FS::cust_bill_pkg_detail { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_bill_pkg_detail object represents additional detail information for
-an invoice line item (see L<FS::cust_bill_pkg>). FS::cust_bill_pkg_detail
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item detailnum - primary key
-
-=item pkgnum -
-
-=item invnum -
-
-=item detail - detail description
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new line item detail. To add the line item detail to the database,
-see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_bill_pkg_detail'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid line item detail. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('detailnum')
- || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
- || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum')
- || $self->ut_text('detail')
- || $self->SUPER::check
- ;
-
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::cust_bill_pkg>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
deleted file mode 100644
index d5b6ff4..0000000
--- a/FS/FS/cust_credit.pm
+++ /dev/null
@@ -1,595 +0,0 @@
-package FS::cust_credit;
-
-use strict;
-use vars qw( @ISA $conf $unsuspendauto $me $DEBUG );
-use Date::Format;
-use FS::UID qw( dbh getotaker );
-use FS::Misc qw(send_email);
-use FS::Record qw( qsearch qsearchs dbdef );
-use FS::cust_main_Mixin;
-use FS::cust_main;
-use FS::cust_refund;
-use FS::cust_credit_bill;
-use FS::part_pkg;
-use FS::reason_type;
-use FS::reason;
-
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-$me = '[ FS::cust_credit ]';
-$DEBUG = 0;
-
-#ask FS::UID to run this stuff for us later
-$FS::UID::callback{'FS::cust_credit'} = sub {
-
- $conf = new FS::Conf;
- $unsuspendauto = $conf->exists('unsuspendauto');
-
-};
-
-our %reasontype_map = ( 'referral_credit_type' => 'Referral Credit',
- 'cancel_credit_type' => 'Cancellation Credit',
- 'signup_credit_type' => 'Self-Service Credit',
- );
-
-=head1 NAME
-
-FS::cust_credit - Object methods for cust_credit records
-
-=head1 SYNOPSIS
-
- use FS::cust_credit;
-
- $record = new FS::cust_credit \%hash;
- $record = new FS::cust_credit { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_credit object represents a credit; the equivalent of a negative
-B<cust_bill> record (see L<FS::cust_bill>). FS::cust_credit inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item crednum
-
-Primary key (assigned automatically for new credits)
-
-=item custnum
-
-Customer (see L<FS::cust_main>)
-
-=item amount
-
-Amount of the credit
-
-=item _date
-
-Specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item otaker
-
-Order taker (assigned automatically, see L<FS::UID>)
-
-=item reason
-
-Text ( deprecated )
-
-=item reasonnum
-
-Reason (see L<FS::reason>)
-
-=item closed
-
-Books closed flag, empty or `Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new credit. To add the credit to the database, see L<"insert">.
-
-=cut
-
-sub table { 'cust_credit'; }
-sub cust_linked { $_[0]->cust_main_custnum; }
-sub cust_unlinked_msg {
- my $self = shift;
- "WARNING: can't find cust_main.custnum ". $self->custnum.
- ' (cust_credit.crednum '. $self->crednum. ')';
-}
-
-=item insert
-
-Adds this credit to the database ("Posts" the credit). If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub insert {
- my ($self, %options) = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $cust_main = qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
- my $old_balance = $cust_main->balance;
-
- unless ($self->reasonnum) {
- my $result = $self->reason( $self->getfield('reason'),
- exists($options{ 'reason_type' })
- ? ('reason_type' => $options{ 'reason_type' })
- : (),
- );
- unless($result) {
- $dbh->rollback if $oldAutoCommit;
- return "failed to set reason for $me: ". $dbh->errstr;
- }
- }
-
- $self->setfield('reason', '');
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error inserting $self: $error";
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- #false laziness w/ cust_credit::insert
- if ( $unsuspendauto && $old_balance && $cust_main->balance <= 0 ) {
- my @errors = $cust_main->unsuspend;
- #return
- # side-fx with nested transactions? upstack rolls back?
- warn "WARNING:Errors unsuspending customer ". $cust_main->custnum. ": ".
- join(' / ', @errors)
- if @errors;
- }
- #eslaf
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-Unless the closed flag is set, deletes this credit and all associated
-applications (see L<FS::cust_credit_bill>). In most cases, you want to use
-the void method instead to leave a record of the deleted credit.
-
-=cut
-
-# very similar to FS::cust_pay::delete
-sub delete {
- my $self = shift;
- return "Can't delete closed credit" if $self->closed =~ /^Y/i;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $cust_credit_bill ( $self->cust_credit_bill ) {
- my $error = $cust_credit_bill->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- foreach my $cust_credit_refund ( $self->cust_credit_refund ) {
- my $error = $cust_credit_refund->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $error = $self->SUPER::delete(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $conf->config('deletecredits') ne '' ) {
-
- my $cust_main = $self->cust_main;
-
- my $error = send_email(
- 'from' => $conf->config('invoice_from'), #??? well as good as any
- 'to' => $conf->config('deletecredits'),
- 'subject' => 'FREESIDE NOTIFICATION: Credit deleted',
- 'body' => [
- "This is an automatic message from your Freeside installation\n",
- "informing you that the following credit has been deleted:\n",
- "\n",
- 'crednum: '. $self->crednum. "\n",
- 'custnum: '. $self->custnum.
- " (". $cust_main->last. ", ". $cust_main->first. ")\n",
- 'amount: $'. sprintf("%.2f", $self->amount). "\n",
- 'date: '. time2str("%a %b %e %T %Y", $self->_date). "\n",
- 'reason: '. $self->reason. "\n",
- ],
- );
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't send credit deletion notification: $error";
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item replace OLD_RECORD
-
-You can, but probably shouldn't modify credits...
-
-=cut
-
-sub replace {
- #return "Can't modify credit!"
- my $self = shift;
- return "Can't modify closed credit" if $self->closed =~ /^Y/i;
- $self->SUPER::replace(@_);
-}
-
-=item check
-
-Checks all fields to make sure this is a valid credit. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->otaker(getotaker) unless ($self->otaker);
-
- my $error =
- $self->ut_numbern('crednum')
- || $self->ut_number('custnum')
- || $self->ut_numbern('_date')
- || $self->ut_money('amount')
- || $self->ut_alpha('otaker')
- || $self->ut_textn('reason')
- || $self->ut_foreign_key('reasonnum', 'reason', 'reasonnum')
- || $self->ut_enum('closed', [ '', 'Y' ])
- ;
- return $error if $error;
-
- return "amount must be > 0 " if $self->amount <= 0;
-
- return "Unknown customer"
- unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-
- $self->_date(time) unless $self->_date;
-
- $self->SUPER::check;
-}
-
-=item cust_credit_refund
-
-Returns all refund applications (see L<FS::cust_credit_refund>) for this credit.
-
-=cut
-
-sub cust_credit_refund {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_credit_refund', { 'crednum' => $self->crednum } )
- ;
-}
-
-=item cust_credit_bill
-
-Returns all application to invoices (see L<FS::cust_credit_bill>) for this
-credit.
-
-=cut
-
-sub cust_credit_bill {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_credit_bill', { 'crednum' => $self->crednum } )
- ;
-}
-
-=item unapplied
-
-Returns the amount of this credit that is still unapplied/outstanding;
-amount minus all refund applications (see L<FS::cust_credit_refund>) and
-applications to invoices (see L<FS::cust_credit_bill>).
-
-=cut
-
-sub unapplied {
- my $self = shift;
- my $amount = $self->amount;
- $amount -= $_->amount foreach ( $self->cust_credit_refund );
- $amount -= $_->amount foreach ( $self->cust_credit_bill );
- sprintf( "%.2f", $amount );
-}
-
-=item credited
-
-Deprecated name for the unapplied method.
-
-=cut
-
-sub credited {
- my $self = shift;
- #carp "cust_credit->credited deprecated; use ->unapplied";
- $self->unapplied(@_);
-}
-
-=item cust_main
-
-Returns the customer (see L<FS::cust_main>) for this credit.
-
-=cut
-
-sub cust_main {
- my $self = shift;
- qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-}
-
-
-=item reason
-
-Returns the text of the associated reason (see L<FS::reason>) for this credit.
-
-=cut
-
-sub reason {
- my ($self, $value, %options) = @_;
- my $dbh = dbh;
- my $reason;
- my $typenum = $options{'reason_type'};
-
- my $oldAutoCommit = $FS::UID::AutoCommit; # this should already be in
- local $FS::UID::AutoCommit = 0; # a transaction if it matters
-
- if ( defined( $value ) ) {
- my $hashref = { 'reason' => $value };
- $hashref->{'reason_type'} = $typenum if $typenum;
- my $addl_from = "LEFT JOIN reason_type ON ( reason_type = typenum ) ";
- my $extra_sql = " AND reason_type.class='R'";
-
- $reason = qsearchs( { 'table' => 'reason',
- 'hashref' => $hashref,
- 'addl_from' => $addl_from,
- 'extra_sql' => $extra_sql,
- } );
-
- if (!$reason && $typenum) {
- $reason = new FS::reason( { 'reason_type' => $typenum,
- 'reason' => $value,
- 'disabled' => 'Y',
- } );
- $reason->insert and $reason = undef;
- }
-
- $self->reasonnum($reason ? $reason->reasonnum : '') ;
- warn "$me reason used in set mode with non-existant reason -- clearing"
- unless $reason;
- }
- $reason = qsearchs( 'reason', { 'reasonnum' => $self->reasonnum } );
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- $reason ? $reason->reason : '';
-}
-
-# _upgrade_data
-#
-# Used by FS::Upgrade to migrate to a new database.
-
-sub _upgrade_data { # class method
- my ($class, %opts) = @_;
-
- warn "$me upgrading $class\n" if $DEBUG;
-
- if (defined dbdef->table($class->table)->column('reason')) {
-
- warn "$me Checking for unmigrated reasons\n" if $DEBUG;
-
- my @cust_credits = qsearch({ 'table' => $class->table,
- 'hashref' => {},
- 'extra_sql' => 'WHERE reason IS NOT NULL',
- });
-
- if (scalar(grep { $_->getfield('reason') =~ /\S/ } @cust_credits)) {
- warn "$me Found unmigrated reasons\n" if $DEBUG;
- my $hashref = { 'class' => 'R', 'type' => 'Legacy' };
- my $reason_type = qsearchs( 'reason_type', $hashref );
- unless ($reason_type) {
- $reason_type = new FS::reason_type( $hashref );
- my $error = $reason_type->insert();
- die "$class had error inserting FS::reason_type into database: $error\n"
- if $error;
- }
-
- $hashref = { 'reason_type' => $reason_type->typenum,
- 'reason' => '(none)'
- };
- my $noreason = qsearchs( 'reason', $hashref );
- unless ($noreason) {
- $hashref->{'disabled'} = 'Y';
- $noreason = new FS::reason( $hashref );
- my $error = $noreason->insert();
- die "can't insert legacy reason '(none)' into database: $error\n"
- if $error;
- }
-
- foreach my $cust_credit ( @cust_credits ) {
- my $reason = $cust_credit->getfield('reason');
- warn "Contemplating reason $reason\n" if $DEBUG > 1;
- if ($reason =~ /\S/) {
- $cust_credit->reason($reason, 'reason_type' => $reason_type->typenum)
- or die "can't insert legacy reason $reason into database\n";
- }else{
- $cust_credit->reasonnum($noreason->reasonnum);
- }
-
- $cust_credit->setfield('reason', '');
- my $error = $cust_credit->replace;
-
- warn "*** WARNING: error replacing reason in $class ".
- $cust_credit->crednum. ": $error ***\n"
- if $error;
- }
- }
-
- warn "$me Ensuring existance of auto reasons\n" if $DEBUG;
-
- foreach ( keys %reasontype_map ) {
- unless ($conf->config($_)) { # hmmmm
-# warn "$me Found $_ reason type lacking\n" if $DEBUG;
-# my $hashref = { 'class' => 'R', 'type' => $reasontype_map{$_} };
- my $hashref = { 'class' => 'R', 'type' => 'Legacy' };
- my $reason_type = qsearchs( 'reason_type', $hashref );
- unless ($reason_type) {
- $reason_type = new FS::reason_type( $hashref );
- my $error = $reason_type->insert();
- die "$class had error inserting FS::reason_type into database: $error\n"
- if $error;
- }
- $conf->set($_, $reason_type->typenum);
- }
- }
-
- warn "$me Ensuring commission packages have a reason type\n" if $DEBUG;
-
- my $hashref = { 'class' => 'R', 'type' => 'Legacy' };
- my $reason_type = qsearchs( 'reason_type', $hashref );
- unless ($reason_type) {
- $reason_type = new FS::reason_type( $hashref );
- my $error = $reason_type->insert();
- die "$class had error inserting FS::reason_type into database: $error\n"
- if $error;
- }
-
- my @plans = qw( flat_comission flat_comission_cust flat_comission_pkg );
- foreach my $plan ( @plans ) {
- foreach my $pkg ( qsearch('part_pkg', { 'plan' => $plan } ) ) {
- unless ($pkg->option('reason_type', 1) ) {
- my $plandata = $pkg->plandata.
- "reason_type=". $reason_type->typenum. "\n";
- $pkg->plandata($plandata);
- my $error =
- $pkg->replace( undef,
- 'pkg_svc' => { map { $_->svcpart => $_->quantity }
- $pkg->pkg_svc
- },
- 'primary_svc' => $pkg->svcpart,
- );
- die "failed setting reason_type option: $error"
- if $error;
- }
- }
- }
- }
-
- '';
-
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item unapplied_sql
-
-Returns an SQL fragment to retreive the unapplied amount.
-
-=cut
-
-sub unapplied_sql {
- #my $class = shift;
-
- "amount
- - COALESCE(
- ( SELECT SUM(amount) FROM cust_credit_refund
- WHERE cust_credit.crednum = cust_credit_refund.crednum )
- ,0
- )
- - COALESCE(
- ( SELECT SUM(amount) FROM cust_credit_bill
- WHERE cust_credit.crednum = cust_credit_bill.crednum )
- ,0
- )
- ";
-
-}
-
-=item credited_sql
-
-Deprecated name for the unapplied_sql method.
-
-=cut
-
-sub credited_sql {
- #my $class = shift;
-
- #carp "cust_credit->credited_sql deprecated; use ->unapplied_sql";
-
- #$class->unapplied_sql(@_);
- unapplied_sql();
-}
-
-=back
-
-=head1 BUGS
-
-The delete method. The replace method.
-
-B<credited> and B<credited_sql> are now called B<unapplied> and
-B<unapplied_sql>. The old method names should start to give warnings.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_credit_refund>, L<FS::cust_refund>,
-L<FS::cust_credit_bill> L<FS::cust_bill>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_credit_bill.pm b/FS/FS/cust_credit_bill.pm
deleted file mode 100644
index 375c885..0000000
--- a/FS/FS/cust_credit_bill.pm
+++ /dev/null
@@ -1,168 +0,0 @@
-package FS::cust_credit_bill;
-
-use strict;
-use vars qw( @ISA $conf );
-use FS::UID qw( getotaker );
-use FS::Record qw( qsearch qsearchs );
-use FS::cust_main_Mixin;
-use FS::cust_bill_ApplicationCommon;
-use FS::cust_bill;
-use FS::cust_credit;
-
-@ISA = qw( FS::cust_main_Mixin FS::cust_bill_ApplicationCommon );
-
-#ask FS::UID to run this stuff for us later
-FS::UID->install_callback( sub {
- $conf = new FS::Conf;
-} );
-
-=head1 NAME
-
-FS::cust_credit_bill - Object methods for cust_credit_bill records
-
-=head1 SYNOPSIS
-
- use FS::cust_credit_bill;
-
- $record = new FS::cust_credit_bill \%hash;
- $record = new FS::cust_credit_bill { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_credit_bill object represents application of a credit (see
-L<FS::cust_credit>) to an invoice (see L<FS::cust_bill>). FS::cust_credit_bill
-inherits from FS::cust_bill_ApplicationCommon and FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item creditbillnum - primary key
-
-=item crednum - credit being applied
-
-=item invnum - invoice to which credit is applied (see L<FS::cust_bill>)
-
-=item amount - amount of the credit applied
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new cust_credit_bill. To add the cust_credit_bill to the database,
-see L<"insert">.
-
-=cut
-
-sub table { 'cust_credit_bill'; }
-
-sub _app_source_name { 'credit'; }
-sub _app_source_table { 'cust_credit'; }
-sub _app_lineitem_breakdown_table { 'cust_credit_bill_pkg'; }
-sub _app_part_pkg_weight_column { 'credit_weight'; }
-
-=item insert
-
-Adds this cust_credit_bill to the database ("Posts" all or part of a credit).
-If there is an error, returns the error, otherwise returns false.
-
-=item delete
-
-Currently unimplemented.
-
-=cut
-
-sub delete {
- my $self = shift;
- return "Can't delete application for closed credit"
- if $self->cust_credit->closed =~ /^Y/i;
- return "Can't delete application for closed invoice"
- if $self->cust_bill->closed =~ /^Y/i;
- $self->SUPER::delete(@_);
-}
-
-=item replace OLD_RECORD
-
-Application of credits may not be modified.
-
-=cut
-
-sub replace {
- return "Can't modify application of credit!"
-}
-
-=item check
-
-Checks all fields to make sure this is a valid credit application. If there
-is an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('creditbillnum')
- || $self->ut_foreign_key('crednum', 'cust_credit', 'crednum')
- || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum' )
- || $self->ut_numbern('_date')
- || $self->ut_money('amount')
- ;
- return $error if $error;
-
- return "amount must be > 0" if $self->amount <= 0;
-
- $self->_date(time) unless $self->_date;
-
- return "Cannot apply more than remaining value of credit"
- unless $self->amount <= $self->cust_credit->credited;
-
- return "Cannot apply more than remaining value of invoice"
- unless $self->amount <= $self->cust_bill->owed;
-
- $self->SUPER::check;
-}
-
-=item sub cust_credit
-
-Returns the credit (see L<FS::cust_credit>)
-
-=cut
-
-sub cust_credit {
- my $self = shift;
- qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
-}
-
-=back
-
-=head1 BUGS
-
-The delete method.
-
-This probably should have been called cust_bill_credit.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_bill>, L<FS::cust_credit>,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_credit_bill_pkg.pm b/FS/FS/cust_credit_bill_pkg.pm
deleted file mode 100644
index 7252be5..0000000
--- a/FS/FS/cust_credit_bill_pkg.pm
+++ /dev/null
@@ -1,141 +0,0 @@
-package FS::cust_credit_bill_pkg;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cust_credit_bill_pkg - Object methods for cust_credit_bill_pkg records
-
-=head1 SYNOPSIS
-
- use FS::cust_credit_bill_pkg;
-
- $record = new FS::cust_credit_bill_pkg \%hash;
- $record = new FS::cust_credit_bill_pkg { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_credit_bill_pkg object represents application of a credit (see
-L<FS::cust_credit_bill>) to a specific line item within an invoice
-(see L<FS::cust_bill_pkg>). FS::cust_credit_bill_pkg inherits from FS::Record.
-The following fields are currently supported:
-
-=over 4
-
-=item creditbillpkg - primary key
-
-=item creditbillnum - Credit application to the overall invoice (see L<FS::cust_credit::bill>)
-
-=item billpkgnum - Line item to which credit is applied (see L<FS::cust_bill_pkg>)
-
-=item amount - Amount of the credit applied to this line item.
-
-=item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item.
-
-=item sdate - starting date of recurring fee
-
-=item edate - ending date of recurring fee
-
-=back
-
-sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">. Also
-see L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new example. To add the example to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_credit_bill_pkg'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid credit applicaiton. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('creditbillpkgnum')
- || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
- || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
- || $self->ut_money('amount')
- || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
- || $self->ut_numbern('sdate')
- || $self->ut_numbern('edate')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
-setup and recur fields. It should be removed once that's fixed.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_credit_refund.pm b/FS/FS/cust_credit_refund.pm
deleted file mode 100644
index f237efe..0000000
--- a/FS/FS/cust_credit_refund.pm
+++ /dev/null
@@ -1,186 +0,0 @@
-package FS::cust_credit_refund;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs dbh );
-#use FS::UID qw(getotaker);
-use FS::cust_credit;
-use FS::cust_refund;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::cust_credit_refund - Object methods for cust_bill_pay records
-
-=head1 SYNOPSIS
-
- use FS::cust_credit_refund;
-
- $record = new FS::cust_credit_refund \%hash;
- $record = new FS::cust_credit_refund { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_credit_refund represents the application of a refund to a specific
-credit. FS::cust_credit_refund inherits from FS::Record. The following fields
-are currently supported:
-
-=over 4
-
-=item creditrefundnum - primary key (assigned automatically)
-
-=item crednum - Credit (see L<FS::cust_credit>)
-
-=item refundnum - Refund (see L<FS::cust_refund>)
-
-=item amount - Amount of the refund to apply to the specific credit.
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-=cut
-
-sub table { 'cust_credit_refund'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub insert {
- my $self = shift;
- return "Can't apply refund to closed credit"
- if $self->cust_credit->closed =~ /^Y/i;
- return "Can't apply credit to closed refund"
- if $self->cust_refund->closed =~ /^Y/i;
- $self->SUPER::insert(@_);
-}
-
-=item delete
-
-Remove this cust_credit_refund from the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub delete {
- my $self = shift;
- return "Can't remove refund from closed credit"
- if $self->cust_credit->closed =~ /^Y/i;
- return "Can't remove credit from closed refund"
- if $self->cust_refund->closed =~ /^Y/i;
- $self->SUPER::delete(@_);
-}
-
-=item replace OLD_RECORD
-
-Currently unimplemented (accounting reasons).
-
-=cut
-
-sub replace {
- return "Can't (yet?) modify cust_credit_refund records!";
-}
-
-=item check
-
-Checks all fields to make sure this is a valid refund application. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-method.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('creditrefundnum')
- || $self->ut_number('crednum')
- || $self->ut_number('refundnum')
- || $self->ut_money('amount')
- || $self->ut_numbern('_date')
- ;
- return $error if $error;
-
- return "amount must be > 0" if $self->amount <= 0;
-
- return "unknown cust_credit.crednum: ". $self->crednum
- unless my $cust_credit =
- qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
-
- return "Unknown refund"
- unless my $cust_refund =
- qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } );
-
- $self->_date(time) unless $self->_date;
-
- return "Cannot apply more than remaining value of credit"
- unless $self->amount <= $cust_credit->credited;
-
- return "Cannot apply more than remaining value of refund"
- unless $self->amount <= $cust_refund->unapplied;
-
- $self->SUPER::check;
-}
-
-=item cust_refund
-
-Returns the refund (see L<FS::cust_refund>)
-
-=cut
-
-sub cust_refund {
- my $self = shift;
- qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } );
-}
-
-=item cust_credit
-
-Returns the credit (see L<FS::cust_credit>)
-
-=cut
-
-sub cust_credit {
- my $self = shift;
- qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
-}
-
-=back
-
-=head1 BUGS
-
-Delete and replace methods.
-
-the checks for over-applied refunds could be better done like the ones in
-cust_bill_credit
-
-=head1 SEE ALSO
-
-L<FS::cust_credit>, L<FS::cust_refund>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_event.pm b/FS/FS/cust_event.pm
deleted file mode 100644
index 5924851..0000000
--- a/FS/FS/cust_event.pm
+++ /dev/null
@@ -1,408 +0,0 @@
-package FS::cust_event;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Carp qw( croak confess );
-use FS::Record qw( qsearch qsearchs dbdef );
-use FS::cust_main_Mixin;
-use FS::part_event;
-#for cust_X
-use FS::cust_main;
-use FS::cust_pkg;
-use FS::cust_bill;
-
-@ISA = qw(FS::cust_main_Mixin FS::Record);
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::cust_event - Object methods for cust_event records
-
-=head1 SYNOPSIS
-
- use FS::cust_event;
-
- $record = new FS::cust_event \%hash;
- $record = new FS::cust_event { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_event object represents an completed event. FS::cust_event
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item eventnum - primary key
-
-=item eventpart - event definition (see L<FS::part_event>)
-
-=item tablenum - customer, package or invoice, depending on the value of part_event.eventtable (see L<FS::cust_main>, L<FS::cust_pkg>, and L<FS::cust_bill>)
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item status - event status: B<new>, B<locked>, B<done> or B<failed>. Note: B<done> indicates the event is complete and should not be retried (statustext may still be set to an optional message), while B<failed> indicates the event failed and should be retried.
-
-=item statustext - additional status detail (i.e. error or progress message)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new completed invoice event. To add the compelted invoice event to
-the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_event'; }
-
-sub cust_linked { $_[0]->cust_main_custnum; }
-sub cust_unlinked_msg {
- my $self = shift;
- "WARNING: can't find cust_main.custnum ". $self->custnum;
- #' (cust_bill.invnum '. $self->invnum. ')';
-}
-sub custnum {
- my $self = shift;
- $self->cust_main_custnum(@_) || $self->SUPER::custnum(@_);
-}
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid completed invoice event. If
-there is an error, returns the error, otherwise returns false. Called by the
-insert and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error = $self->ut_numbern('eventnum')
- || $self->ut_foreign_key('eventpart', 'part_event', 'eventpart')
- ;
- return $error if $error;
-
- my $eventtable = $self->part_event->eventtable;
- my $dbdef_eventtable = dbdef->table( $eventtable );
-
- $error =
- $self->ut_foreign_key( 'tablenum',
- $eventtable,
- $dbdef_eventtable->primary_key
- )
- || $self->ut_number('_date')
- || $self->ut_enum('status', [qw( new locked done failed )])
- || $self->ut_anything('statustext')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item part_event
-
-Returns the event definition (see L<FS::part_event>) for this completed event.
-
-=cut
-
-sub part_event {
- my $self = shift;
- qsearchs( 'part_event', { 'eventpart' => $self->eventpart } );
-}
-
-=item cust_X
-
-Returns the customer, package, invoice or batched payment (see
-L<FS::cust_main>, L<FS::cust_pkg>, L<FS::cust_bill> or L<FS::cust_pay_batch>)
-for this completed invoice event.
-
-=cut
-
-sub cust_bill {
- croak "FS::cust_event::cust_bill called";
-}
-
-sub cust_X {
- my $self = shift;
- my $eventtable = $self->part_event->eventtable;
- my $dbdef_table = dbdef->table( $eventtable );
- my $primary_key = $dbdef_table->primary_key;
- qsearchs( $eventtable, { $primary_key => $self->tablenum } );
-}
-
-=item test_conditions [ OPTION => VALUE ... ]
-
-Tests conditions for this event, returns true if all conditions are satisfied,
-false otherwise.
-
-=cut
-
-sub test_conditions {
- my( $self, %opt ) = @_;
- my $part_event = $self->part_event;
- my $object = $self->cust_X;
- my @conditions = $part_event->part_event_condition;
- $opt{'cust_event'} = $self;
-
- #no unsatisfied conditions
- #! grep ! $_->condition( $object, %opt ), @conditions;
- my @unsatisfied = grep ! $_->condition( $object, %opt ), @conditions;
-
- if ( $opt{'stats_hashref'} ) {
- foreach my $unsat (@unsatisfied) {
- $opt{'stats_hashref'}->{$unsat->conditionname}++;
- }
- }
-
- ! @unsatisfied;
-}
-
-=item do_event
-
-Runs the event action.
-
-=cut
-
-sub do_event {
- my $self = shift;
-
- my $part_event = $self->part_event;
-
- my $object = $self->cust_X;
- my $obj_pkey = $object->primary_key;
- my $for = "for ". $object->table. " ". $object->$obj_pkey();
- warn "running cust_event ". $self->eventnum.
- " (". $part_event->action. ") $for\n"
- if $DEBUG;
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
-
- my $error;
- {
- local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
- $error = eval { $part_event->do_action($object); };
- }
-
- my $status = '';
- my $statustext = '';
- if ( $@ ) {
- $status = 'failed';
- #$statustext = $@;
- $statustext = "Error running ". $part_event->action. " action: $@";
- } elsif ( $error ) {
- $status = 'done';
- $statustext = $error;
- } else {
- $status = 'done';
- }
-
- #replace or add myself
- $self->_date(time);
- $self->status($status);
- $self->statustext($statustext);
-
- $error = $self->eventnum ? $self->replace : $self->insert;
- if ( $error ) {
- #this is why we need that locked state...
- my $e = 'WARNING: Event run but database not updated - '.
- 'error replacing or inserting cust_event '. $self->eventnum.
- " $for: $error\n";
- warn $e;
- return $e;
- }
-
- '';
-
-}
-
-=item retry
-
-Changes the status of this event from B<done> to B<failed>, allowing it to be
-retried.
-
-=cut
-
-sub retry {
- my $self = shift;
- return '' unless $self->status eq 'done';
- my $old = ref($self)->new( { $self->hash } );
- $self->status('failed');
- $self->replace($old);
-}
-
-#=item retryable
-#
-#Changes the statustext of this event to B<retriable>, rendering it
-#retriable (should retry be called).
-#
-#=cut
-
-sub retriable {
- confess "cust_event->retriable called";
- my $self = shift;
- return '' unless $self->status eq 'done';
- my $old = ref($self)->new( { $self->hash } );
- $self->statustext('retriable');
- $self->replace($old);
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item reprint
-
-=cut
-
-sub process_reprint {
- process_re_X('print', @_);
-}
-
-=item reemail
-
-=cut
-
-sub process_reemail {
- process_re_X('email', @_);
-}
-
-=item refax
-
-=cut
-
-sub process_refax {
- process_re_X('fax', @_);
-}
-
-use Storable qw(thaw);
-use Data::Dumper;
-use MIME::Base64;
-sub process_re_X {
- my( $method, $job ) = ( shift, shift );
-
- my $param = thaw(decode_base64(shift));
- warn Dumper($param) if $DEBUG;
-
- re_X(
- $method,
- $param->{'beginning'},
- $param->{'ending'},
- $param->{'failed'},
- $job,
- );
-
-}
-
-sub re_X {
- my($method, $beginning, $ending, $failed, $job) = @_;
-
- my $from = 'LEFT JOIN part_event USING ( eventpart )';
-
- # yuck! hardcoed *AND* sequential scans!
- my $where = " WHERE action LIKE 'cust_bill_send%'".
- " AND cust_event._date >= $beginning".
- " AND cust_event._date <= $ending";
- $where .= " AND statustext != '' AND statustext IS NOT NULL"
- if $failed;
-
- my @cust_event = qsearch({
- 'table' => 'cust_event',
- 'addl_from' => $from,
- 'hashref' => {},
- 'extra_sql' => $where,
- });
-
- my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
- foreach my $cust_event ( @cust_event ) {
-
- # XXX
- $cust_event->cust_bill->$method(
- $cust_event->part_event->templatename
- || $cust_event->cust_main->agent_template
- );
-
- if ( $job ) { #progressbar foo
- $num++;
- if ( time - $min_sec > $last ) {
- my $error = $job->update_statustext(
- int( 100 * $num / scalar(@cust_event) )
- );
- die $error if $error;
- $last = time;
- }
- }
-
- }
-
- #this doesn't work, but it would be nice
- #if ( $job ) { #progressbar foo
- # my $error = $job->update_statustext(
- # scalar(@cust_event). " invoices re-${method}ed"
- # );
- # die $error if $error;
- #}
-
-}
-
-=back
-
-=head1 SEE ALSO
-
-L<FS::part_event>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
deleted file mode 100644
index b58a52c..0000000
--- a/FS/FS/cust_main.pm
+++ /dev/null
@@ -1,6250 +0,0 @@
-package FS::cust_main;
-
-require 5.006;
-use strict;
-use vars qw( @ISA @EXPORT_OK $DEBUG $me $conf @encrypted_fields
- $import $skip_fuzzyfiles $ignore_expired_card @paytypes);
-use vars qw( $realtime_bop_decline_quiet ); #ugh
-use Safe;
-use Carp;
-use Exporter;
-use Time::Local qw(timelocal_nocheck);
-use Data::Dumper;
-use Tie::IxHash;
-use Digest::MD5 qw(md5_base64);
-use Date::Format;
-use Date::Parse;
-#use Date::Manip;
-use String::Approx qw(amatch);
-use Business::CreditCard 0.28;
-use Locale::Country;
-use Data::Dumper;
-use FS::UID qw( getotaker dbh driver_name );
-use FS::Record qw( qsearchs qsearch dbdef );
-use FS::Misc qw( send_email generate_ps do_print );
-use FS::Msgcat qw(gettext);
-use FS::cust_pkg;
-use FS::cust_svc;
-use FS::cust_bill;
-use FS::cust_bill_pkg;
-use FS::cust_pay;
-use FS::cust_pay_pending;
-use FS::cust_pay_void;
-use FS::cust_pay_batch;
-use FS::cust_credit;
-use FS::cust_refund;
-use FS::part_referral;
-use FS::cust_main_county;
-use FS::agent;
-use FS::cust_main_invoice;
-use FS::cust_credit_bill;
-use FS::cust_bill_pay;
-use FS::prepay_credit;
-use FS::queue;
-use FS::part_pkg;
-use FS::part_event;
-use FS::part_event_condition;
-#use FS::cust_event;
-use FS::cust_tax_exempt;
-use FS::cust_tax_exempt_pkg;
-use FS::type_pkgs;
-use FS::payment_gateway;
-use FS::agent_payment_gateway;
-use FS::banned_pay;
-use FS::payinfo_Mixin;
-use FS::TicketSystem;
-
-@ISA = qw( FS::Record FS::payinfo_Mixin );
-
-@EXPORT_OK = qw( smart_search );
-
-$realtime_bop_decline_quiet = 0;
-
-# 1 is mostly method/subroutine entry and options
-# 2 traces progress of some operations
-# 3 is even more information including possibly sensitive data
-$DEBUG = 0;
-$me = '[FS::cust_main]';
-
-$import = 0;
-$skip_fuzzyfiles = 0;
-$ignore_expired_card = 0;
-
-@encrypted_fields = ('payinfo', 'paycvv');
-@paytypes = ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings');
-
-#ask FS::UID to run this stuff for us later
-#$FS::UID::callback{'FS::cust_main'} = sub {
-install_callback FS::UID sub {
- $conf = new FS::Conf;
- #yes, need it for stuff below (prolly should be cached)
-};
-
-sub _cache {
- my $self = shift;
- my ( $hashref, $cache ) = @_;
- if ( exists $hashref->{'pkgnum'} ) {
- #@{ $self->{'_pkgnum'} } = ();
- my $subcache = $cache->subcache( 'pkgnum', 'cust_pkg', $hashref->{custnum});
- $self->{'_pkgnum'} = $subcache;
- #push @{ $self->{'_pkgnum'} },
- FS::cust_pkg->new_or_cached($hashref, $subcache) if $hashref->{pkgnum};
- }
-}
-
-=head1 NAME
-
-FS::cust_main - Object methods for cust_main records
-
-=head1 SYNOPSIS
-
- use FS::cust_main;
-
- $record = new FS::cust_main \%hash;
- $record = new FS::cust_main { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- @cust_pkg = $record->all_pkgs;
-
- @cust_pkg = $record->ncancelled_pkgs;
-
- @cust_pkg = $record->suspended_pkgs;
-
- $error = $record->bill;
- $error = $record->bill %options;
- $error = $record->bill 'time' => $time;
-
- $error = $record->collect;
- $error = $record->collect %options;
- $error = $record->collect 'invoice_time' => $time,
- ;
-
-=head1 DESCRIPTION
-
-An FS::cust_main object represents a customer. FS::cust_main inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item custnum - primary key (assigned automatically for new customers)
-
-=item agentnum - agent (see L<FS::agent>)
-
-=item refnum - Advertising source (see L<FS::part_referral>)
-
-=item first - name
-
-=item last - name
-
-=item ss - social security number (optional)
-
-=item company - (optional)
-
-=item address1
-
-=item address2 - (optional)
-
-=item city
-
-=item county - (optional, see L<FS::cust_main_county>)
-
-=item state - (see L<FS::cust_main_county>)
-
-=item zip
-
-=item country - (see L<FS::cust_main_county>)
-
-=item daytime - phone (optional)
-
-=item night - phone (optional)
-
-=item fax - phone (optional)
-
-=item ship_first - name
-
-=item ship_last - name
-
-=item ship_company - (optional)
-
-=item ship_address1
-
-=item ship_address2 - (optional)
-
-=item ship_city
-
-=item ship_county - (optional, see L<FS::cust_main_county>)
-
-=item ship_state - (see L<FS::cust_main_county>)
-
-=item ship_zip
-
-=item ship_country - (see L<FS::cust_main_county>)
-
-=item ship_daytime - phone (optional)
-
-=item ship_night - phone (optional)
-
-=item ship_fax - phone (optional)
-
-=item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
-
-=item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format)
-
-=item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
-
-=item paycvv
-
-Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card
-
-=item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
-
-=item paystart_month - start date month (maestro/solo cards only)
-
-=item paystart_year - start date year (maestro/solo cards only)
-
-=item payissue - issue number (maestro/solo cards only)
-
-=item payname - name on card or billing name
-
-=item payip - IP address from which payment information was received
-
-=item tax - tax exempt, empty or `Y'
-
-=item otaker - order taker (assigned automatically, see L<FS::UID>)
-
-=item comments - comments (optional)
-
-=item referral_custnum - referring customer number
-
-=item spool_cdr - Enable individual CDR spooling, empty or `Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new customer. To add the customer to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'cust_main'; }
-
-=item insert [ CUST_PKG_HASHREF [ , INVOICING_LIST_ARYREF ] [ , OPTION => VALUE ... ] ]
-
-Adds this customer to the database. If there is an error, returns the error,
-otherwise returns false.
-
-CUST_PKG_HASHREF: If you pass a Tie::RefHash data structure to the insert
-method containing FS::cust_pkg and FS::svc_I<tablename> objects, all records
-are inserted atomicly, or the transaction is rolled back. Passing an empty
-hash reference is equivalent to not supplying this parameter. There should be
-a better explanation of this, but until then, here's an example:
-
- use Tie::RefHash;
- tie %hash, 'Tie::RefHash'; #this part is important
- %hash = (
- $cust_pkg => [ $svc_acct ],
- ...
- );
- $cust_main->insert( \%hash );
-
-INVOICING_LIST_ARYREF: If you pass an arrarref to the insert method, it will
-be set as the invoicing list (see L<"invoicing_list">). Errors return as
-expected and rollback the entire transaction; it is not necessary to call
-check_invoicing_list first. The invoicing_list is set after the records in the
-CUST_PKG_HASHREF above are inserted, so it is now possible to set an
-invoicing_list destination to the newly-created svc_acct. Here's an example:
-
- $cust_main->insert( {}, [ $email, 'POST' ] );
-
-Currently available options are: I<depend_jobnum> and I<noexport>.
-
-If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
-on the supplied jobnum (they will not run until the specific job completes).
-This can be used to defer provisioning until some action completes (such
-as running the customer's credit card successfully).
-
-The I<noexport> option is deprecated. If I<noexport> is set true, no
-provisioning jobs (exports) are scheduled. (You can schedule them later with
-the B<reexport> method.)
-
-=cut
-
-sub insert {
- my $self = shift;
- my $cust_pkgs = @_ ? shift : {};
- my $invoicing_list = @_ ? shift : '';
- my %options = @_;
- warn "$me insert called with options ".
- join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $prepay_identifier = '';
- my( $amount, $seconds ) = ( 0, 0 );
- my $payby = '';
- if ( $self->payby eq 'PREPAY' ) {
-
- $self->payby('BILL');
- $prepay_identifier = $self->payinfo;
- $self->payinfo('');
-
- warn " looking up prepaid card $prepay_identifier\n"
- if $DEBUG > 1;
-
- my $error = $self->get_prepay($prepay_identifier, \$amount, \$seconds);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- #return "error applying prepaid card (transaction rolled back): $error";
- return $error;
- }
-
- $payby = 'PREP' if $amount;
-
- } elsif ( $self->payby =~ /^(CASH|WEST|MCRD)$/ ) {
-
- $payby = $1;
- $self->payby('BILL');
- $amount = $self->paid;
-
- }
-
- warn " inserting $self\n"
- if $DEBUG > 1;
-
- $self->signupdate(time) unless $self->signupdate;
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- #return "inserting cust_main record (transaction rolled back): $error";
- return $error;
- }
-
- warn " setting invoicing list\n"
- if $DEBUG > 1;
-
- if ( $invoicing_list ) {
- $error = $self->check_invoicing_list( $invoicing_list );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- #return "checking invoicing_list (transaction rolled back): $error";
- return $error;
- }
- $self->invoicing_list( $invoicing_list );
- }
-
- if ( $conf->config('cust_main-skeleton_tables')
- && $conf->config('cust_main-skeleton_custnum') ) {
-
- warn " inserting skeleton records\n"
- if $DEBUG > 1;
-
- my $error = $self->start_copy_skel;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- warn " ordering packages\n"
- if $DEBUG > 1;
-
- $error = $self->order_pkgs($cust_pkgs, \$seconds, %options);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $seconds ) {
- $dbh->rollback if $oldAutoCommit;
- return "No svc_acct record to apply pre-paid time";
- }
-
- if ( $amount ) {
- warn " inserting initial $payby payment of $amount\n"
- if $DEBUG > 1;
- $error = $self->insert_cust_pay($payby, $amount, $prepay_identifier);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "inserting payment (transaction rolled back): $error";
- }
- }
-
- unless ( $import || $skip_fuzzyfiles ) {
- warn " queueing fuzzyfiles update\n"
- if $DEBUG > 1;
- $error = $self->queue_fuzzyfiles_update;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "updating fuzzy search cache: $error";
- }
- }
-
- warn " insert complete; committing transaction\n"
- if $DEBUG > 1;
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-sub start_copy_skel {
- my $self = shift;
-
- #'mg_user_preference' => {},
- #'mg_user_indicator_profile.user_indicator_profile_id' => { 'mg_profile_indicator.profile_indicator_id' => { 'mg_profile_details.profile_detail_id' }, },
- #'mg_watchlist_header.watchlist_header_id' => { 'mg_watchlist_details.watchlist_details_id' },
- #'mg_user_grid_header.grid_header_id' => { 'mg_user_grid_details.user_grid_details_id' },
- #'mg_portfolio_header.portfolio_header_id' => { 'mg_portfolio_trades.portfolio_trades_id' => { 'mg_portfolio_trades_positions.portfolio_trades_positions_id' } },
- my @tables = eval(join('\n',$conf->config('cust_main-skeleton_tables')));
- die $@ if $@;
-
- _copy_skel( 'cust_main', #tablename
- $conf->config('cust_main-skeleton_custnum'), #sourceid
- $self->custnum, #destid
- @tables, #child tables
- );
-}
-
-#recursive subroutine, not a method
-sub _copy_skel {
- my( $table, $sourceid, $destid, %child_tables ) = @_;
-
- my $primary_key;
- if ( $table =~ /^(\w+)\.(\w+)$/ ) {
- ( $table, $primary_key ) = ( $1, $2 );
- } else {
- my $dbdef_table = dbdef->table($table);
- $primary_key = $dbdef_table->primary_key
- or return "$table has no primary key".
- " (or do you need to run dbdef-create?)";
- }
-
- warn " _copy_skel: $table.$primary_key $sourceid to $destid for ".
- join (', ', keys %child_tables). "\n"
- if $DEBUG > 2;
-
- foreach my $child_table_def ( keys %child_tables ) {
-
- my $child_table;
- my $child_pkey = '';
- if ( $child_table_def =~ /^(\w+)\.(\w+)$/ ) {
- ( $child_table, $child_pkey ) = ( $1, $2 );
- } else {
- $child_table = $child_table_def;
-
- $child_pkey = dbdef->table($child_table)->primary_key;
- # or return "$table has no primary key".
- # " (or do you need to run dbdef-create?)\n";
- }
-
- my $sequence = '';
- if ( keys %{ $child_tables{$child_table_def} } ) {
-
- return "$child_table has no primary key".
- " (run dbdef-create or try specifying it?)\n"
- unless $child_pkey;
-
- #false laziness w/Record::insert and only works on Pg
- #refactor the proper last-inserted-id stuff out of Record::insert if this
- # ever gets use for anything besides a quick kludge for one customer
- my $default = dbdef->table($child_table)->column($child_pkey)->default;
- $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i
- or return "can't parse $child_table.$child_pkey default value ".
- " for sequence name: $default";
- $sequence = $1;
-
- }
-
- my @sel_columns = grep { $_ ne $primary_key }
- dbdef->table($child_table)->columns;
- my $sel_columns = join(', ', @sel_columns );
-
- my @ins_columns = grep { $_ ne $child_pkey } @sel_columns;
- my $ins_columns = ' ( '. join(', ', $primary_key, @ins_columns ). ' ) ';
- my $placeholders = ' ( ?, '. join(', ', map '?', @ins_columns ). ' ) ';
-
- my $sel_st = "SELECT $sel_columns FROM $child_table".
- " WHERE $primary_key = $sourceid";
- warn " $sel_st\n"
- if $DEBUG > 2;
- my $sel_sth = dbh->prepare( $sel_st )
- or return dbh->errstr;
-
- $sel_sth->execute or return $sel_sth->errstr;
-
- while ( my $row = $sel_sth->fetchrow_hashref ) {
-
- warn " selected row: ".
- join(', ', map { "$_=".$row->{$_} } keys %$row ). "\n"
- if $DEBUG > 2;
-
- my $statement =
- "INSERT INTO $child_table $ins_columns VALUES $placeholders";
- my $ins_sth =dbh->prepare($statement)
- or return dbh->errstr;
- my @param = ( $destid, map $row->{$_}, @ins_columns );
- warn " $statement: [ ". join(', ', @param). " ]\n"
- if $DEBUG > 2;
- $ins_sth->execute( @param )
- or return $ins_sth->errstr;
-
- #next unless keys %{ $child_tables{$child_table} };
- next unless $sequence;
-
- #another section of that laziness
- my $seq_sql = "SELECT currval('$sequence')";
- my $seq_sth = dbh->prepare($seq_sql) or return dbh->errstr;
- $seq_sth->execute or return $seq_sth->errstr;
- my $insertid = $seq_sth->fetchrow_arrayref->[0];
-
- # don't drink soap! recurse! recurse! okay!
- my $error =
- _copy_skel( $child_table_def,
- $row->{$child_pkey}, #sourceid
- $insertid, #destid
- %{ $child_tables{$child_table_def} },
- );
- return $error if $error;
-
- }
-
- }
-
- return '';
-
-}
-
-=item order_pkgs HASHREF, [ SECONDSREF, [ , OPTION => VALUE ... ] ]
-
-Like the insert method on an existing record, this method orders a package
-and included services atomicaly. Pass a Tie::RefHash data structure to this
-method containing FS::cust_pkg and FS::svc_I<tablename> objects. There should
-be a better explanation of this, but until then, here's an example:
-
- use Tie::RefHash;
- tie %hash, 'Tie::RefHash'; #this part is important
- %hash = (
- $cust_pkg => [ $svc_acct ],
- ...
- );
- $cust_main->order_pkgs( \%hash, \'0', 'noexport'=>1 );
-
-Services can be new, in which case they are inserted, or existing unaudited
-services, in which case they are linked to the newly-created package.
-
-Currently available options are: I<depend_jobnum> and I<noexport>.
-
-If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
-on the supplied jobnum (they will not run until the specific job completes).
-This can be used to defer provisioning until some action completes (such
-as running the customer's credit card successfully).
-
-The I<noexport> option is deprecated. If I<noexport> is set true, no
-provisioning jobs (exports) are scheduled. (You can schedule them later with
-the B<reexport> method for each cust_pkg object. Using the B<reexport> method
-on the cust_main object is not recommended, as existing services will also be
-reexported.)
-
-=cut
-
-sub order_pkgs {
- my $self = shift;
- my $cust_pkgs = shift;
- my $seconds = shift;
- my %options = @_;
- my %svc_options = ();
- $svc_options{'depend_jobnum'} = $options{'depend_jobnum'}
- if exists $options{'depend_jobnum'};
- warn "$me order_pkgs called with options ".
- join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'};
-
- foreach my $cust_pkg ( keys %$cust_pkgs ) {
- $cust_pkg->custnum( $self->custnum );
- my $error = $cust_pkg->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "inserting cust_pkg (transaction rolled back): $error";
- }
- foreach my $svc_something ( @{$cust_pkgs->{$cust_pkg}} ) {
- if ( $svc_something->svcnum ) {
- my $old_cust_svc = $svc_something->cust_svc;
- my $new_cust_svc = new FS::cust_svc { $old_cust_svc->hash };
- $new_cust_svc->pkgnum( $cust_pkg->pkgnum);
- $error = $new_cust_svc->replace($old_cust_svc);
- } else {
- $svc_something->pkgnum( $cust_pkg->pkgnum );
- if ( $seconds && $$seconds && $svc_something->isa('FS::svc_acct') ) {
- $svc_something->seconds( $svc_something->seconds + $$seconds );
- $$seconds = 0;
- }
- $error = $svc_something->insert(%svc_options);
- }
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- #return "inserting svc_ (transaction rolled back): $error";
- return $error;
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-}
-
-=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF, UPBYTEREF, DOWNBYTEREF ]
-
-Recharges this (existing) customer with the specified prepaid card (see
-L<FS::prepay_credit>), specified either by I<identifier> or as an
-FS::prepay_credit object. If there is an error, returns the error, otherwise
-returns false.
-
-Optionally, four scalar references can be passed as well. They will have their
-values filled in with the amount, number of seconds, and number of upload and
-download bytes applied by this prepaid
-card.
-
-=cut
-
-sub recharge_prepay {
- my( $self, $prepay_credit, $amountref, $secondsref,
- $upbytesref, $downbytesref, $totalbytesref ) = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my( $amount, $seconds, $upbytes, $downbytes, $totalbytes) = ( 0, 0, 0, 0, 0 );
-
- my $error = $self->get_prepay($prepay_credit, \$amount,
- \$seconds, \$upbytes, \$downbytes, \$totalbytes)
- || $self->increment_seconds($seconds)
- || $self->increment_upbytes($upbytes)
- || $self->increment_downbytes($downbytes)
- || $self->increment_totalbytes($totalbytes)
- || $self->insert_cust_pay_prepay( $amount,
- ref($prepay_credit)
- ? $prepay_credit->identifier
- : $prepay_credit
- );
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( defined($amountref) ) { $$amountref = $amount; }
- if ( defined($secondsref) ) { $$secondsref = $seconds; }
- if ( defined($upbytesref) ) { $$upbytesref = $upbytes; }
- if ( defined($downbytesref) ) { $$downbytesref = $downbytes; }
- if ( defined($totalbytesref) ) { $$totalbytesref = $totalbytes; }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item get_prepay IDENTIFIER | PREPAY_CREDIT_OBJ , AMOUNTREF, SECONDSREF
-
-Looks up and deletes a prepaid card (see L<FS::prepay_credit>),
-specified either by I<identifier> or as an FS::prepay_credit object.
-
-References to I<amount> and I<seconds> scalars should be passed as arguments
-and will be incremented by the values of the prepaid card.
-
-If the prepaid card specifies an I<agentnum> (see L<FS::agent>), it is used to
-check or set this customer's I<agentnum>.
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-
-sub get_prepay {
- my( $self, $prepay_credit, $amountref, $secondsref,
- $upref, $downref, $totalref) = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- unless ( ref($prepay_credit) ) {
-
- my $identifier = $prepay_credit;
-
- $prepay_credit = qsearchs(
- 'prepay_credit',
- { 'identifier' => $prepay_credit },
- '',
- 'FOR UPDATE'
- );
-
- unless ( $prepay_credit ) {
- $dbh->rollback if $oldAutoCommit;
- return "Invalid prepaid card: ". $identifier;
- }
-
- }
-
- if ( $prepay_credit->agentnum ) {
- if ( $self->agentnum && $self->agentnum != $prepay_credit->agentnum ) {
- $dbh->rollback if $oldAutoCommit;
- return "prepaid card not valid for agent ". $self->agentnum;
- }
- $self->agentnum($prepay_credit->agentnum);
- }
-
- my $error = $prepay_credit->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "removing prepay_credit (transaction rolled back): $error";
- }
-
- $$amountref += $prepay_credit->amount;
- $$secondsref += $prepay_credit->seconds;
- $$upref += $prepay_credit->upbytes;
- $$downref += $prepay_credit->downbytes;
- $$totalref += $prepay_credit->totalbytes;
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item increment_upbytes SECONDS
-
-Updates this customer's single or primary account (see L<FS::svc_acct>) by
-the specified number of upbytes. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub increment_upbytes {
- _increment_column( shift, 'upbytes', @_);
-}
-
-=item increment_downbytes SECONDS
-
-Updates this customer's single or primary account (see L<FS::svc_acct>) by
-the specified number of downbytes. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub increment_downbytes {
- _increment_column( shift, 'downbytes', @_);
-}
-
-=item increment_totalbytes SECONDS
-
-Updates this customer's single or primary account (see L<FS::svc_acct>) by
-the specified number of totalbytes. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub increment_totalbytes {
- _increment_column( shift, 'totalbytes', @_);
-}
-
-=item increment_seconds SECONDS
-
-Updates this customer's single or primary account (see L<FS::svc_acct>) by
-the specified number of seconds. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub increment_seconds {
- _increment_column( shift, 'seconds', @_);
-}
-
-=item _increment_column AMOUNT
-
-Updates this customer's single or primary account (see L<FS::svc_acct>) by
-the specified number of seconds or bytes. If there is an error, returns
-the error, otherwise returns false.
-
-=cut
-
-sub _increment_column {
- my( $self, $column, $amount ) = @_;
- warn "$me increment_column called: $column, $amount\n"
- if $DEBUG;
-
- return '' unless $amount;
-
- my @cust_pkg = grep { $_->part_pkg->svcpart('svc_acct') }
- $self->ncancelled_pkgs;
-
- if ( ! @cust_pkg ) {
- return 'No packages with primary or single services found'.
- ' to apply pre-paid time';
- } elsif ( scalar(@cust_pkg) > 1 ) {
- #maybe have a way to specify the package/account?
- return 'Multiple packages found to apply pre-paid time';
- }
-
- my $cust_pkg = $cust_pkg[0];
- warn " found package pkgnum ". $cust_pkg->pkgnum. "\n"
- if $DEBUG > 1;
-
- my @cust_svc =
- $cust_pkg->cust_svc( $cust_pkg->part_pkg->svcpart('svc_acct') );
-
- if ( ! @cust_svc ) {
- return 'No account found to apply pre-paid time';
- } elsif ( scalar(@cust_svc) > 1 ) {
- return 'Multiple accounts found to apply pre-paid time';
- }
-
- my $svc_acct = $cust_svc[0]->svc_x;
- warn " found service svcnum ". $svc_acct->pkgnum.
- ' ('. $svc_acct->email. ")\n"
- if $DEBUG > 1;
-
- $column = "increment_$column";
- $svc_acct->$column($amount);
-
-}
-
-=item insert_cust_pay_prepay AMOUNT [ PAYINFO ]
-
-Inserts a prepayment in the specified amount for this customer. An optional
-second argument can specify the prepayment identifier for tracking purposes.
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub insert_cust_pay_prepay {
- shift->insert_cust_pay('PREP', @_);
-}
-
-=item insert_cust_pay_cash AMOUNT [ PAYINFO ]
-
-Inserts a cash payment in the specified amount for this customer. An optional
-second argument can specify the payment identifier for tracking purposes.
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub insert_cust_pay_cash {
- shift->insert_cust_pay('CASH', @_);
-}
-
-=item insert_cust_pay_west AMOUNT [ PAYINFO ]
-
-Inserts a Western Union payment in the specified amount for this customer. An
-optional second argument can specify the prepayment identifier for tracking
-purposes. If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub insert_cust_pay_west {
- shift->insert_cust_pay('WEST', @_);
-}
-
-sub insert_cust_pay {
- my( $self, $payby, $amount ) = splice(@_, 0, 3);
- my $payinfo = scalar(@_) ? shift : '';
-
- my $cust_pay = new FS::cust_pay {
- 'custnum' => $self->custnum,
- 'paid' => sprintf('%.2f', $amount),
- #'_date' => #date the prepaid card was purchased???
- 'payby' => $payby,
- 'payinfo' => $payinfo,
- };
- $cust_pay->insert;
-
-}
-
-=item reexport
-
-This method is deprecated. See the I<depend_jobnum> option to the insert and
-order_pkgs methods for a better way to defer provisioning.
-
-Re-schedules all exports by calling the B<reexport> method of all associated
-packages (see L<FS::cust_pkg>). If there is an error, returns the error;
-otherwise returns false.
-
-=cut
-
-sub reexport {
- my $self = shift;
-
- carp "WARNING: FS::cust_main::reexport is deprectated; ".
- "use the depend_jobnum option to insert or order_pkgs to delay export";
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $cust_pkg ( $self->ncancelled_pkgs ) {
- my $error = $cust_pkg->reexport;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item delete NEW_CUSTNUM
-
-This deletes the customer. If there is an error, returns the error, otherwise
-returns false.
-
-This will completely remove all traces of the customer record. This is not
-what you want when a customer cancels service; for that, cancel all of the
-customer's packages (see L</cancel>).
-
-If the customer has any uncancelled packages, you need to pass a new (valid)
-customer number for those packages to be transferred to. Cancelled packages
-will be deleted. Did I mention that this is NOT what you want when a customer
-cancels service and that you really should be looking see L<FS::cust_pkg/cancel>?
-
-You can't delete a customer with invoices (see L<FS::cust_bill>),
-or credits (see L<FS::cust_credit>), payments (see L<FS::cust_pay>) or
-refunds (see L<FS::cust_refund>).
-
-=cut
-
-sub delete {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ( $self->cust_bill ) {
- $dbh->rollback if $oldAutoCommit;
- return "Can't delete a customer with invoices";
- }
- if ( $self->cust_credit ) {
- $dbh->rollback if $oldAutoCommit;
- return "Can't delete a customer with credits";
- }
- if ( $self->cust_pay ) {
- $dbh->rollback if $oldAutoCommit;
- return "Can't delete a customer with payments";
- }
- if ( $self->cust_refund ) {
- $dbh->rollback if $oldAutoCommit;
- return "Can't delete a customer with refunds";
- }
-
- my @cust_pkg = $self->ncancelled_pkgs;
- if ( @cust_pkg ) {
- my $new_custnum = shift;
- unless ( qsearchs( 'cust_main', { 'custnum' => $new_custnum } ) ) {
- $dbh->rollback if $oldAutoCommit;
- return "Invalid new customer number: $new_custnum";
- }
- foreach my $cust_pkg ( @cust_pkg ) {
- my %hash = $cust_pkg->hash;
- $hash{'custnum'} = $new_custnum;
- my $new_cust_pkg = new FS::cust_pkg ( \%hash );
- my $error = $new_cust_pkg->replace($cust_pkg,
- options => { $cust_pkg->options },
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
- my @cancelled_cust_pkg = $self->all_pkgs;
- foreach my $cust_pkg ( @cancelled_cust_pkg ) {
- my $error = $cust_pkg->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- foreach my $cust_main_invoice ( #(email invoice destinations, not invoices)
- qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } )
- ) {
- my $error = $cust_main_invoice->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item replace OLD_RECORD [ INVOICING_LIST_ARYREF ]
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-INVOICING_LIST_ARYREF: If you pass an arrarref to the insert method, it will
-be set as the invoicing list (see L<"invoicing_list">). Errors return as
-expected and rollback the entire transaction; it is not necessary to call
-check_invoicing_list first. Here's an example:
-
- $new_cust_main->replace( $old_cust_main, [ $email, 'POST' ] );
-
-=cut
-
-sub replace {
- my $self = shift;
- my $old = shift;
- my @param = @_;
- warn "$me replace called\n"
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- # We absolutely have to have an old vs. new record to make this work.
- if (!defined($old)) {
- $old = qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
- }
-
- my $curuser = $FS::CurrentUser::CurrentUser;
- if ( $self->payby eq 'COMP'
- && $self->payby ne $old->payby
- && ! $curuser->access_right('Complimentary customer')
- )
- {
- return "You are not permitted to create complimentary accounts.";
- }
-
- local($ignore_expired_card) = 1
- if $old->payby =~ /^(CARD|DCRD)$/
- && $self->payby =~ /^(CARD|DCRD)$/
- && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::replace($old);
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( @param ) { # INVOICING_LIST_ARYREF
- my $invoicing_list = shift @param;
- $error = $self->check_invoicing_list( $invoicing_list );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $self->invoicing_list( $invoicing_list );
- }
-
- if ( $self->payby =~ /^(CARD|CHEK|LECB)$/ &&
- grep { $self->get($_) ne $old->get($_) } qw(payinfo paydate payname) ) {
- # card/check/lec info has changed, want to retry realtime_ invoice events
- my $error = $self->retry_realtime;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- unless ( $import || $skip_fuzzyfiles ) {
- $error = $self->queue_fuzzyfiles_update;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "updating fuzzy search cache: $error";
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item queue_fuzzyfiles_update
-
-Used by insert & replace to update the fuzzy search cache
-
-=cut
-
-sub queue_fuzzyfiles_update {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' };
- my $error = $queue->insert( map $self->getfield($_),
- qw(first last company)
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "queueing job (transaction rolled back): $error";
- }
-
- if ( $self->ship_last ) {
- $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' };
- $error = $queue->insert( map $self->getfield("ship_$_"),
- qw(first last company)
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "queueing job (transaction rolled back): $error";
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid customer record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- warn "$me check BEFORE: \n". $self->_dump
- if $DEBUG > 2;
-
- my $error =
- $self->ut_numbern('custnum')
- || $self->ut_number('agentnum')
- || $self->ut_textn('agent_custid')
- || $self->ut_number('refnum')
- || $self->ut_name('last')
- || $self->ut_name('first')
- || $self->ut_snumbern('birthdate')
- || $self->ut_snumbern('signupdate')
- || $self->ut_textn('company')
- || $self->ut_text('address1')
- || $self->ut_textn('address2')
- || $self->ut_text('city')
- || $self->ut_textn('county')
- || $self->ut_textn('state')
- || $self->ut_country('country')
- || $self->ut_anything('comments')
- || $self->ut_numbern('referral_custnum')
- || $self->ut_textn('stateid')
- || $self->ut_textn('stateid_state')
- || $self->ut_textn('invoice_terms')
- ;
- #barf. need message catalogs. i18n. etc.
- $error .= "Please select an advertising source."
- if $error =~ /^Illegal or empty \(numeric\) refnum: /;
- return $error if $error;
-
- return "Unknown agent"
- unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
-
- return "Unknown refnum"
- unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
-
- return "Unknown referring custnum: ". $self->referral_custnum
- unless ! $self->referral_custnum
- || qsearchs( 'cust_main', { 'custnum' => $self->referral_custnum } );
-
- if ( $self->ss eq '' ) {
- $self->ss('');
- } else {
- my $ss = $self->ss;
- $ss =~ s/\D//g;
- $ss =~ /^(\d{3})(\d{2})(\d{4})$/
- or return "Illegal social security number: ". $self->ss;
- $self->ss("$1-$2-$3");
- }
-
-
-# bad idea to disable, causes billing to fail because of no tax rates later
-# unless ( $import ) {
- unless ( qsearch('cust_main_county', {
- 'country' => $self->country,
- 'state' => '',
- } ) ) {
- return "Unknown state/county/country: ".
- $self->state. "/". $self->county. "/". $self->country
- unless qsearch('cust_main_county',{
- 'state' => $self->state,
- 'county' => $self->county,
- 'country' => $self->country,
- } );
- }
-# }
-
- $error =
- $self->ut_phonen('daytime', $self->country)
- || $self->ut_phonen('night', $self->country)
- || $self->ut_phonen('fax', $self->country)
- || $self->ut_zip('zip', $self->country)
- ;
- return $error if $error;
-
- if ( $conf->exists('cust_main-require_phone')
- && ! length($self->daytime) && ! length($self->night)
- ) {
-
- my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
- ? 'Day Phone'
- : FS::Msgcat::_gettext('daytime');
- my $night_label = FS::Msgcat::_gettext('night') =~ /^(night)?$/
- ? 'Night Phone'
- : FS::Msgcat::_gettext('night');
-
- return "$daytime_label or $night_label is required"
-
- }
-
- if ( $self->has_ship_address
- && scalar ( grep { $self->getfield($_) ne $self->getfield("ship_$_") }
- $self->addr_fields )
- )
- {
- my $error =
- $self->ut_name('ship_last')
- || $self->ut_name('ship_first')
- || $self->ut_textn('ship_company')
- || $self->ut_text('ship_address1')
- || $self->ut_textn('ship_address2')
- || $self->ut_text('ship_city')
- || $self->ut_textn('ship_county')
- || $self->ut_textn('ship_state')
- || $self->ut_country('ship_country')
- ;
- return $error if $error;
-
- #false laziness with above
- unless ( qsearchs('cust_main_county', {
- 'country' => $self->ship_country,
- 'state' => '',
- } ) ) {
- return "Unknown ship_state/ship_county/ship_country: ".
- $self->ship_state. "/". $self->ship_county. "/". $self->ship_country
- unless qsearch('cust_main_county',{
- 'state' => $self->ship_state,
- 'county' => $self->ship_county,
- 'country' => $self->ship_country,
- } );
- }
- #eofalse
-
- $error =
- $self->ut_phonen('ship_daytime', $self->ship_country)
- || $self->ut_phonen('ship_night', $self->ship_country)
- || $self->ut_phonen('ship_fax', $self->ship_country)
- || $self->ut_zip('ship_zip', $self->ship_country)
- ;
- return $error if $error;
-
- return "Unit # is required."
- if $self->ship_address2 =~ /^\s*$/
- && $conf->exists('cust_main-require_address2');
-
- } else { # ship_ info eq billing info, so don't store dup info in database
-
- $self->setfield("ship_$_", '')
- foreach $self->addr_fields;
-
- return "Unit # is required."
- if $self->address2 =~ /^\s*$/
- && $conf->exists('cust_main-require_address2');
-
- }
-
- #$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/
- # or return "Illegal payby: ". $self->payby;
- #$self->payby($1);
- FS::payby->can_payby($self->table, $self->payby)
- or return "Illegal payby: ". $self->payby;
-
- $error = $self->ut_numbern('paystart_month')
- || $self->ut_numbern('paystart_year')
- || $self->ut_numbern('payissue')
- || $self->ut_textn('paytype')
- ;
- return $error if $error;
-
- if ( $self->payip eq '' ) {
- $self->payip('');
- } else {
- $error = $self->ut_ip('payip');
- return $error if $error;
- }
-
- # If it is encrypted and the private key is not availaible then we can't
- # check the credit card.
-
- my $check_payinfo = 1;
-
- if ($self->is_encrypted($self->payinfo)) {
- $check_payinfo = 0;
- }
-
- if ( $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
-
- my $payinfo = $self->payinfo;
- $payinfo =~ s/\D//g;
- $payinfo =~ /^(\d{13,16})$/
- or return gettext('invalid_card'); # . ": ". $self->payinfo;
- $payinfo = $1;
- $self->payinfo($payinfo);
- validate($payinfo)
- or return gettext('invalid_card'); # . ": ". $self->payinfo;
-
- return gettext('unknown_card_type')
- if cardtype($self->payinfo) eq "Unknown";
-
- my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
- if ( $ban ) {
- return 'Banned credit card: banned on '.
- time2str('%a %h %o at %r', $ban->_date).
- ' by '. $ban->otaker.
- ' (ban# '. $ban->bannum. ')';
- }
-
- if (length($self->paycvv) && !$self->is_encrypted($self->paycvv)) {
- if ( cardtype($self->payinfo) eq 'American Express card' ) {
- $self->paycvv =~ /^(\d{4})$/
- or return "CVV2 (CID) for American Express cards is four digits.";
- $self->paycvv($1);
- } else {
- $self->paycvv =~ /^(\d{3})$/
- or return "CVV2 (CVC2/CID) is three digits.";
- $self->paycvv($1);
- }
- } else {
- $self->paycvv('');
- }
-
- my $cardtype = cardtype($payinfo);
- if ( $cardtype =~ /^(Switch|Solo)$/i ) {
-
- return "Start date or issue number is required for $cardtype cards"
- unless $self->paystart_month && $self->paystart_year or $self->payissue;
-
- return "Start month must be between 1 and 12"
- if $self->paystart_month
- and $self->paystart_month < 1 || $self->paystart_month > 12;
-
- return "Start year must be 1990 or later"
- if $self->paystart_year
- and $self->paystart_year < 1990;
-
- return "Issue number must be beween 1 and 99"
- if $self->payissue
- and $self->payissue < 1 || $self->payissue > 99;
-
- } else {
- $self->paystart_month('');
- $self->paystart_year('');
- $self->payissue('');
- }
-
- } elsif ( $check_payinfo && $self->payby =~ /^(CHEK|DCHK)$/ ) {
-
- my $payinfo = $self->payinfo;
- $payinfo =~ s/[^\d\@]//g;
- if ( $conf->exists('echeck-nonus') ) {
- $payinfo =~ /^(\d+)\@(\d+)$/ or return 'invalid echeck account@aba';
- $payinfo = "$1\@$2";
- } else {
- $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account@aba';
- $payinfo = "$1\@$2";
- }
- $self->payinfo($payinfo);
- $self->paycvv('');
-
- my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
- if ( $ban ) {
- return 'Banned ACH account: banned on '.
- time2str('%a %h %o at %r', $ban->_date).
- ' by '. $ban->otaker.
- ' (ban# '. $ban->bannum. ')';
- }
-
- } elsif ( $self->payby eq 'LECB' ) {
-
- my $payinfo = $self->payinfo;
- $payinfo =~ s/\D//g;
- $payinfo =~ /^1?(\d{10})$/ or return 'invalid btn billing telephone number';
- $payinfo = $1;
- $self->payinfo($payinfo);
- $self->paycvv('');
-
- } elsif ( $self->payby eq 'BILL' ) {
-
- $error = $self->ut_textn('payinfo');
- return "Illegal P.O. number: ". $self->payinfo if $error;
- $self->paycvv('');
-
- } elsif ( $self->payby eq 'COMP' ) {
-
- my $curuser = $FS::CurrentUser::CurrentUser;
- if ( ! $self->custnum
- && ! $curuser->access_right('Complimentary customer')
- )
- {
- return "You are not permitted to create complimentary accounts."
- }
-
- $error = $self->ut_textn('payinfo');
- return "Illegal comp account issuer: ". $self->payinfo if $error;
- $self->paycvv('');
-
- } elsif ( $self->payby eq 'PREPAY' ) {
-
- my $payinfo = $self->payinfo;
- $payinfo =~ s/\W//g; #anything else would just confuse things
- $self->payinfo($payinfo);
- $error = $self->ut_alpha('payinfo');
- return "Illegal prepayment identifier: ". $self->payinfo if $error;
- return "Unknown prepayment identifier"
- unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
- $self->paycvv('');
-
- }
-
- if ( $self->paydate eq '' || $self->paydate eq '-' ) {
- return "Expiration date required"
- unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD)$/;
- $self->paydate('');
- } else {
- my( $m, $y );
- if ( $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/ ) {
- ( $m, $y ) = ( $1, length($2) == 4 ? $2 : "20$2" );
- } elsif ( $self->paydate =~ /^(20)?(\d{2})[\/\-](\d{1,2})[\/\-]\d+$/ ) {
- ( $m, $y ) = ( $3, "20$2" );
- } else {
- return "Illegal expiration date: ". $self->paydate;
- }
- $self->paydate("$y-$m-01");
- my($nowm,$nowy)=(localtime(time))[4,5]; $nowm++; $nowy+=1900;
- return gettext('expired_card')
- if !$import
- && !$ignore_expired_card
- && ( $y<$nowy || ( $y==$nowy && $1<$nowm ) );
- }
-
- if ( $self->payname eq '' && $self->payby !~ /^(CHEK|DCHK)$/ &&
- ( ! $conf->exists('require_cardname')
- || $self->payby !~ /^(CARD|DCRD)$/ )
- ) {
- $self->payname( $self->first. " ". $self->getfield('last') );
- } else {
- $self->payname =~ /^([\w \,\.\-\'\&]+)$/
- or return gettext('illegal_name'). " payname: ". $self->payname;
- $self->payname($1);
- }
-
- foreach my $flag (qw( tax spool_cdr )) {
- $self->$flag() =~ /^(Y?)$/ or return "Illegal $flag: ". $self->$flag();
- $self->$flag($1);
- }
-
- $self->otaker(getotaker) unless $self->otaker;
-
- warn "$me check AFTER: \n". $self->_dump
- if $DEBUG > 2;
-
- $self->SUPER::check;
-}
-
-=item addr_fields
-
-Returns a list of fields which have ship_ duplicates.
-
-=cut
-
-sub addr_fields {
- qw( last first company
- address1 address2 city county state zip country
- daytime night fax
- );
-}
-
-=item has_ship_address
-
-Returns true if this customer record has a separate shipping address.
-
-=cut
-
-sub has_ship_address {
- my $self = shift;
- scalar( grep { $self->getfield("ship_$_") ne '' } $self->addr_fields );
-}
-
-=item all_pkgs
-
-Returns all packages (see L<FS::cust_pkg>) for this customer.
-
-=cut
-
-sub all_pkgs {
- my $self = shift;
-
- return $self->num_pkgs unless wantarray;
-
- my @cust_pkg = ();
- if ( $self->{'_pkgnum'} ) {
- @cust_pkg = values %{ $self->{'_pkgnum'}->cache };
- } else {
- @cust_pkg = qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
- }
-
- sort sort_packages @cust_pkg;
-}
-
-=item cust_pkg
-
-Synonym for B<all_pkgs>.
-
-=cut
-
-sub cust_pkg {
- shift->all_pkgs(@_);
-}
-
-=item ncancelled_pkgs
-
-Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
-
-=cut
-
-sub ncancelled_pkgs {
- my $self = shift;
-
- return $self->num_ncancelled_pkgs unless wantarray;
-
- my @cust_pkg = ();
- if ( $self->{'_pkgnum'} ) {
-
- warn "$me ncancelled_pkgs: returning cached objects"
- if $DEBUG > 1;
-
- @cust_pkg = grep { ! $_->getfield('cancel') }
- values %{ $self->{'_pkgnum'}->cache };
-
- } else {
-
- warn "$me ncancelled_pkgs: searching for packages with custnum ".
- $self->custnum. "\n"
- if $DEBUG > 1;
-
- @cust_pkg =
- qsearch( 'cust_pkg', {
- 'custnum' => $self->custnum,
- 'cancel' => '',
- });
- push @cust_pkg,
- qsearch( 'cust_pkg', {
- 'custnum' => $self->custnum,
- 'cancel' => 0,
- });
- }
-
- sort sort_packages @cust_pkg;
-
-}
-
-# This should be generalized to use config options to determine order.
-sub sort_packages {
- if ( $a->get('cancel') and $b->get('cancel') ) {
- $a->pkgnum <=> $b->pkgnum;
- } elsif ( $a->get('cancel') or $b->get('cancel') ) {
- return -1 if $b->get('cancel');
- return 1 if $a->get('cancel');
- return 0;
- } else {
- $a->pkgnum <=> $b->pkgnum;
- }
-}
-
-=item suspended_pkgs
-
-Returns all suspended packages (see L<FS::cust_pkg>) for this customer.
-
-=cut
-
-sub suspended_pkgs {
- my $self = shift;
- grep { $_->susp } $self->ncancelled_pkgs;
-}
-
-=item unflagged_suspended_pkgs
-
-Returns all unflagged suspended packages (see L<FS::cust_pkg>) for this
-customer (thouse packages without the `manual_flag' set).
-
-=cut
-
-sub unflagged_suspended_pkgs {
- my $self = shift;
- return $self->suspended_pkgs
- unless dbdef->table('cust_pkg')->column('manual_flag');
- grep { ! $_->manual_flag } $self->suspended_pkgs;
-}
-
-=item unsuspended_pkgs
-
-Returns all unsuspended (and uncancelled) packages (see L<FS::cust_pkg>) for
-this customer.
-
-=cut
-
-sub unsuspended_pkgs {
- my $self = shift;
- grep { ! $_->susp } $self->ncancelled_pkgs;
-}
-
-=item num_cancelled_pkgs
-
-Returns the number of cancelled packages (see L<FS::cust_pkg>) for this
-customer.
-
-=cut
-
-sub num_cancelled_pkgs {
- shift->num_pkgs("cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0");
-}
-
-sub num_ncancelled_pkgs {
- shift->num_pkgs("( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )");
-}
-
-sub num_pkgs {
- my( $self ) = shift;
- my $sql = scalar(@_) ? shift : '';
- $sql = "AND $sql" if $sql && $sql !~ /^\s*$/ && $sql !~ /^\s*AND/i;
- my $sth = dbh->prepare(
- "SELECT COUNT(*) FROM cust_pkg WHERE custnum = ? $sql"
- ) or die dbh->errstr;
- $sth->execute($self->custnum) or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
-
-=item unsuspend
-
-Unsuspends all unflagged suspended packages (see L</unflagged_suspended_pkgs>
-and L<FS::cust_pkg>) for this customer. Always returns a list: an empty list
-on success or a list of errors.
-
-=cut
-
-sub unsuspend {
- my $self = shift;
- grep { $_->unsuspend } $self->suspended_pkgs;
-}
-
-=item suspend
-
-Suspends all unsuspended packages (see L<FS::cust_pkg>) for this customer.
-
-Returns a list: an empty list on success or a list of errors.
-
-=cut
-
-sub suspend {
- my $self = shift;
- grep { $_->suspend(@_) } $self->unsuspended_pkgs;
-}
-
-=item suspend_if_pkgpart HASHREF | PKGPART [ , PKGPART ... ]
-
-Suspends all unsuspended packages (see L<FS::cust_pkg>) matching the listed
-PKGPARTs (see L<FS::part_pkg>). Preferred usage is to pass a hashref instead
-of a list of pkgparts; the hashref has the following keys:
-
-=over 4
-
-=item pkgparts - listref of pkgparts
-
-=item (other options are passed to the suspend method)
-
-=back
-
-
-Returns a list: an empty list on success or a list of errors.
-
-=cut
-
-sub suspend_if_pkgpart {
- my $self = shift;
- my (@pkgparts, %opt);
- if (ref($_[0]) eq 'HASH'){
- @pkgparts = @{$_[0]{pkgparts}};
- %opt = %{$_[0]};
- }else{
- @pkgparts = @_;
- }
- grep { $_->suspend(%opt) }
- grep { my $pkgpart = $_->pkgpart; grep { $pkgpart eq $_ } @pkgparts }
- $self->unsuspended_pkgs;
-}
-
-=item suspend_unless_pkgpart HASHREF | PKGPART [ , PKGPART ... ]
-
-Suspends all unsuspended packages (see L<FS::cust_pkg>) unless they match the
-given PKGPARTs (see L<FS::part_pkg>). Preferred usage is to pass a hashref
-instead of a list of pkgparts; the hashref has the following keys:
-
-=over 4
-
-=item pkgparts - listref of pkgparts
-
-=item (other options are passed to the suspend method)
-
-=back
-
-Returns a list: an empty list on success or a list of errors.
-
-=cut
-
-sub suspend_unless_pkgpart {
- my $self = shift;
- my (@pkgparts, %opt);
- if (ref($_[0]) eq 'HASH'){
- @pkgparts = @{$_[0]{pkgparts}};
- %opt = %{$_[0]};
- }else{
- @pkgparts = @_;
- }
- grep { $_->suspend(%opt) }
- grep { my $pkgpart = $_->pkgpart; ! grep { $pkgpart eq $_ } @pkgparts }
- $self->unsuspended_pkgs;
-}
-
-=item cancel [ OPTION => VALUE ... ]
-
-Cancels all uncancelled packages (see L<FS::cust_pkg>) for this customer.
-
-Available options are:
-
-=over 4
-
-=item quiet - can be set true to supress email cancellation notices.
-
-=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
-
-=item ban - can be set true to ban this customer's credit card or ACH information, if present.
-
-=back
-
-Always returns a list: an empty list on success or a list of errors.
-
-=cut
-
-sub cancel {
- my( $self, %opt ) = @_;
-
- warn "$me cancel called on customer ". $self->custnum. " with options ".
- join(', ', map { "$_: $opt{$_}" } keys %opt ). "\n"
- if $DEBUG;
-
- return ( 'access denied' )
- unless $FS::CurrentUser::CurrentUser->access_right('Cancel customer');
-
- if ( $opt{'ban'} && $self->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) {
-
- #should try decryption (we might have the private key)
- # and if not maybe queue a job for the server that does?
- return ( "Can't (yet) ban encrypted credit cards" )
- if $self->is_encrypted($self->payinfo);
-
- my $ban = new FS::banned_pay $self->_banned_pay_hashref;
- my $error = $ban->insert;
- return ( $error ) if $error;
-
- }
-
- my @pkgs = $self->ncancelled_pkgs;
-
- warn "$me cancelling ". scalar($self->ncancelled_pkgs). "/".
- scalar(@pkgs). " packages for customer ". $self->custnum. "\n"
- if $DEBUG;
-
- grep { $_ } map { $_->cancel(%opt) } $self->ncancelled_pkgs;
-}
-
-sub _banned_pay_hashref {
- my $self = shift;
-
- my %payby2ban = (
- 'CARD' => 'CARD',
- 'DCRD' => 'CARD',
- 'CHEK' => 'CHEK',
- 'DCHK' => 'CHEK'
- );
-
- {
- 'payby' => $payby2ban{$self->payby},
- 'payinfo' => md5_base64($self->payinfo),
- #don't ever *search* on reason! #'reason' =>
- };
-}
-
-=item notes
-
-Returns all notes (see L<FS::cust_main_note>) for this customer.
-
-=cut
-
-sub notes {
- my $self = shift;
- #order by?
- qsearch( 'cust_main_note',
- { 'custnum' => $self->custnum },
- '',
- 'ORDER BY _DATE DESC'
- );
-}
-
-=item agent
-
-Returns the agent (see L<FS::agent>) for this customer.
-
-=cut
-
-sub agent {
- my $self = shift;
- qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
-}
-
-=item bill_and_collect
-
-Cancels and suspends any packages due, generates bills, applies payments and
-cred
-
-Warns on errors (Does not currently: If there is an error, returns the error, otherwise returns false.)
-
-Options are passed as name-value pairs. Currently available options are:
-
-=over 4
-
-=item time
-
-Bills the customer as if it were that time. Specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion functions. For example:
-
- use Date::Parse;
- ...
- $cust_main->bill( 'time' => str2time('April 20th, 2001') );
-
-=item invoice_time
-
-Used in conjunction with the I<time> option, this option specifies the date of for the generated invoices. Other calculations, such as whether or not to generate the invoice in the first place, are not affected.
-
-=item check_freq
-
-"1d" for the traditional, daily events (the default), or "1m" for the new monthly events (part_event.check_freq)
-
-=item resetup
-
-If set true, re-charges setup fees.
-
-=item debug
-
-Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
-
-=back
-
-=cut
-
-sub bill_and_collect {
- my( $self, %options ) = @_;
-
- ###
- # cancel packages
- ###
-
- #$^T not $options{time} because freeside-daily -d is for pre-printing invoices
- foreach my $cust_pkg (
- grep { $_->expire && $_->expire <= $^T } $self->ncancelled_pkgs
- ) {
- my $error = $cust_pkg->cancel;
- warn "Error cancelling expired pkg ". $cust_pkg->pkgnum.
- " for custnum ". $self->custnum. ": $error"
- if $error;
- }
-
- ###
- # suspend packages
- ###
-
- #$^T not $options{time} because freeside-daily -d is for pre-printing invoices
- foreach my $cust_pkg (
- grep { ( $_->part_pkg->is_prepaid && $_->bill && $_->bill < $^T
- || $_->adjourn && $_->adjourn <= $^T
- )
- && ! $_->susp
- }
- $self->ncancelled_pkgs
- ) {
- my $error = $cust_pkg->suspend;
- warn "Error suspending package ". $cust_pkg->pkgnum.
- " for custnum ". $self->custnum. ": $error"
- if $error;
- }
-
- ###
- # bill and collect
- ###
-
- my $error = $self->bill( %options );
- warn "Error billing, custnum ". $self->custnum. ": $error" if $error;
-
- $self->apply_payments_and_credits;
-
- $error = $self->collect( %options );
- warn "Error collecting, custnum". $self->custnum. ": $error" if $error;
-
-}
-
-=item bill OPTIONS
-
-Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
-conjunction with the collect method by calling B<bill_and_collect>.
-
-If there is an error, returns the error, otherwise returns false.
-
-Options are passed as name-value pairs. Currently available options are:
-
-=over 4
-
-=item resetup
-
-If set true, re-charges setup fees.
-
-=item time
-
-Bills the customer as if it were that time. Specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion functions. For example:
-
- use Date::Parse;
- ...
- $cust_main->bill( 'time' => str2time('April 20th, 2001') );
-
-=item pkg_list
-
-An array ref of specific packages (objects) to attempt billing, instead trying all of them.
-
- $cust_main->bill( pkg_list => [$pkg1, $pkg2] );
-
-=item invoice_time
-
-Used in conjunction with the I<time> option, this option specifies the date of for the generated invoices. Other calculations, such as whether or not to generate the invoice in the first place, are not affected.
-
-=back
-
-=cut
-
-sub bill {
- my( $self, %options ) = @_;
- return '' if $self->payby eq 'COMP';
- warn "$me bill customer ". $self->custnum. "\n"
- if $DEBUG;
-
- my $time = $options{'time'} || time;
-
- my $error;
-
- #put below somehow?
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $self->select_for_update; #mutex
-
- #create a new invoice
- #(we'll remove it later if it doesn't actually need to be generated [contains
- # no line items] and we're inside a transaciton so nothing else will see it)
- my $cust_bill = new FS::cust_bill ( {
- 'custnum' => $self->custnum,
- '_date' => ( $options{'invoice_time'} || $time ),
- #'charged' => $charged,
- 'charged' => 0,
- } );
- $error = $cust_bill->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't create invoice for customer #". $self->custnum. ": $error";
- }
- my $invnum = $cust_bill->invnum;
-
- ###
- # find the packages which are due for billing, find out how much they are
- # & generate invoice database.
- ###
-
- my( $total_setup, $total_recur ) = ( 0, 0 );
- my %tax;
- my @precommit_hooks = ();
-
- foreach my $cust_pkg (
- qsearch('cust_pkg', { 'custnum' => $self->custnum } )
- ) {
-
- #NO!! next if $cust_pkg->cancel;
- next if $cust_pkg->getfield('cancel');
-
- warn " bill package ". $cust_pkg->pkgnum. "\n" if $DEBUG > 1;
-
- #? to avoid use of uninitialized value errors... ?
- $cust_pkg->setfield('bill', '')
- unless defined($cust_pkg->bill);
-
- my $part_pkg = $cust_pkg->part_pkg;
-
- my %hash = $cust_pkg->hash;
- my $old_cust_pkg = new FS::cust_pkg \%hash;
-
- my @details = ();
-
- ###
- # bill setup
- ###
-
- my $setup = 0;
- if ( ! $cust_pkg->setup &&
- (
- ( $conf->exists('disable_setup_suspended_pkgs') &&
- ! $cust_pkg->getfield('susp')
- ) || ! $conf->exists('disable_setup_suspended_pkgs')
- )
- || $options{'resetup'}
- ) {
-
- warn " bill setup\n" if $DEBUG > 1;
-
- $setup = eval { $cust_pkg->calc_setup( $time, \@details ) };
- if ( $@ ) {
- $dbh->rollback if $oldAutoCommit;
- return "$@ running calc_setup for $cust_pkg\n";
- }
-
- $cust_pkg->setfield('setup', $time) unless $cust_pkg->setup;
- }
-
- ###
- # bill recurring fee
- ###
-
- my $recur = 0;
- my $sdate;
- if ( $part_pkg->getfield('freq') ne '0' &&
- ! $cust_pkg->getfield('susp') &&
- ( $cust_pkg->getfield('bill') || 0 ) <= $time
- ) {
-
- # XXX should this be a package event? probably. events are called
- # at collection time at the moment, though...
- if ( $part_pkg->can('reset_usage') ) {
- warn " resetting usage counters" if $DEBUG > 1;
- $part_pkg->reset_usage($cust_pkg);
- }
-
- warn " bill recur\n" if $DEBUG > 1;
-
- # XXX shared with $recur_prog
- $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
-
- #over two params! lets at least switch to a hashref for the rest...
- my %param = ( 'precommit_hooks' => \@precommit_hooks, );
-
- $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details, \%param ) };
- if ( $@ ) {
- $dbh->rollback if $oldAutoCommit;
- return "$@ running calc_recur for $cust_pkg\n";
- }
-
- #change this bit to use Date::Manip? CAREFUL with timezones (see
- # mailing list archive)
- my ($sec,$min,$hour,$mday,$mon,$year) =
- (localtime($sdate) )[0,1,2,3,4,5];
-
- #pro-rating magic - if $recur_prog fiddles $sdate, want to use that
- # only for figuring next bill date, nothing else, so, reset $sdate again
- # here
- $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
- $cust_pkg->last_bill($sdate);
-
- if ( $part_pkg->freq =~ /^\d+$/ ) {
- $mon += $part_pkg->freq;
- until ( $mon < 12 ) { $mon -= 12; $year++; }
- } elsif ( $part_pkg->freq =~ /^(\d+)w$/ ) {
- my $weeks = $1;
- $mday += $weeks * 7;
- } elsif ( $part_pkg->freq =~ /^(\d+)d$/ ) {
- my $days = $1;
- $mday += $days;
- } elsif ( $part_pkg->freq =~ /^(\d+)h$/ ) {
- my $hours = $1;
- $hour += $hours;
- } else {
- $dbh->rollback if $oldAutoCommit;
- return "unparsable frequency: ". $part_pkg->freq;
- }
- $cust_pkg->setfield('bill',
- timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year));
- }
-
- warn "\$setup is undefined" unless defined($setup);
- warn "\$recur is undefined" unless defined($recur);
- warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill);
-
- ###
- # If $cust_pkg has been modified, update it and create cust_bill_pkg records
- ###
-
- if ( $cust_pkg->modified ) { # hmmm.. and if the options are modified?
-
- warn " package ". $cust_pkg->pkgnum. " modified; updating\n"
- if $DEBUG >1;
-
- $error=$cust_pkg->replace($old_cust_pkg,
- options => { $cust_pkg->options },
- );
- if ( $error ) { #just in case
- $dbh->rollback if $oldAutoCommit;
- return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error";
- }
-
- $setup = sprintf( "%.2f", $setup );
- $recur = sprintf( "%.2f", $recur );
- if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
- $dbh->rollback if $oldAutoCommit;
- return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
- }
- if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
- $dbh->rollback if $oldAutoCommit;
- return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
- }
-
- if ( $setup != 0 || $recur != 0 ) {
-
- warn " charges (setup=$setup, recur=$recur); adding line items\n"
- if $DEBUG > 1;
- my $cust_bill_pkg = new FS::cust_bill_pkg ({
- 'invnum' => $invnum,
- 'pkgnum' => $cust_pkg->pkgnum,
- 'setup' => $setup,
- 'recur' => $recur,
- 'sdate' => $sdate,
- 'edate' => $cust_pkg->bill,
- 'details' => \@details,
- });
- $error = $cust_bill_pkg->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't create invoice line item for invoice #$invnum: $error";
- }
- $total_setup += $setup;
- $total_recur += $recur;
-
- ###
- # handle taxes
- ###
-
- unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) {
-
- my $prefix =
- ( $conf->exists('tax-ship_address') && length($self->ship_last) )
- ? 'ship_'
- : '';
- my %taxhash = map { $_ => $self->get("$prefix$_") }
- qw( state county country );
-
- $taxhash{'taxclass'} = $part_pkg->taxclass;
-
- my @taxes = qsearch( 'cust_main_county', \%taxhash );
-
- unless ( @taxes ) {
- $taxhash{'taxclass'} = '';
- @taxes = qsearch( 'cust_main_county', \%taxhash );
- }
-
- #one more try at a whole-country tax rate
- unless ( @taxes ) {
- $taxhash{$_} = '' foreach qw( state county );
- @taxes = qsearch( 'cust_main_county', \%taxhash );
- }
-
- # maybe eliminate this entirely, along with all the 0% records
- unless ( @taxes ) {
- $dbh->rollback if $oldAutoCommit;
- return
- "fatal: can't find tax rate for state/county/country/taxclass ".
- join('/', ( map $self->get("$prefix$_"),
- qw(state county country)
- ),
- $part_pkg->taxclass ). "\n";
- }
-
- foreach my $tax ( @taxes ) {
-
- my $taxable_charged = 0;
- $taxable_charged += $setup
- unless $part_pkg->setuptax =~ /^Y$/i
- || $tax->setuptax =~ /^Y$/i;
- $taxable_charged += $recur
- unless $part_pkg->recurtax =~ /^Y$/i
- || $tax->recurtax =~ /^Y$/i;
- next unless $taxable_charged;
-
- if ( $tax->exempt_amount && $tax->exempt_amount > 0 ) {
- #my ($mon,$year) = (localtime($sdate) )[4,5];
- my ($mon,$year) = (localtime( $sdate || $cust_bill->_date ) )[4,5];
- $mon++;
- my $freq = $part_pkg->freq || 1;
- if ( $freq !~ /(\d+)$/ ) {
- $dbh->rollback if $oldAutoCommit;
- return "daily/weekly package definitions not (yet?)".
- " compatible with monthly tax exemptions";
- }
- my $taxable_per_month =
- sprintf("%.2f", $taxable_charged / $freq );
-
- #call the whole thing off if this customer has any old
- #exemption records...
- my @cust_tax_exempt =
- qsearch( 'cust_tax_exempt' => { custnum=> $self->custnum } );
- if ( @cust_tax_exempt ) {
- $dbh->rollback if $oldAutoCommit;
- return
- 'this customer still has old-style tax exemption records; '.
- 'run bin/fs-migrate-cust_tax_exempt?';
- }
-
- foreach my $which_month ( 1 .. $freq ) {
-
- #maintain the new exemption table now
- my $sql = "
- SELECT SUM(amount)
- FROM cust_tax_exempt_pkg
- LEFT JOIN cust_bill_pkg USING ( billpkgnum )
- LEFT JOIN cust_bill USING ( invnum )
- WHERE custnum = ?
- AND taxnum = ?
- AND year = ?
- AND month = ?
- ";
- my $sth = dbh->prepare($sql) or do {
- $dbh->rollback if $oldAutoCommit;
- return "fatal: can't lookup exising exemption: ". dbh->errstr;
- };
- $sth->execute(
- $self->custnum,
- $tax->taxnum,
- 1900+$year,
- $mon,
- ) or do {
- $dbh->rollback if $oldAutoCommit;
- return "fatal: can't lookup exising exemption: ". dbh->errstr;
- };
- my $existing_exemption = $sth->fetchrow_arrayref->[0] || 0;
-
- my $remaining_exemption =
- $tax->exempt_amount - $existing_exemption;
- if ( $remaining_exemption > 0 ) {
- my $addl = $remaining_exemption > $taxable_per_month
- ? $taxable_per_month
- : $remaining_exemption;
- $taxable_charged -= $addl;
-
- my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg ( {
- 'billpkgnum' => $cust_bill_pkg->billpkgnum,
- 'taxnum' => $tax->taxnum,
- 'year' => 1900+$year,
- 'month' => $mon,
- 'amount' => sprintf("%.2f", $addl ),
- } );
- $error = $cust_tax_exempt_pkg->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "fatal: can't insert cust_tax_exempt_pkg: $error";
- }
- } # if $remaining_exemption > 0
-
- #++
- $mon++;
- #until ( $mon < 12 ) { $mon -= 12; $year++; }
- until ( $mon < 13 ) { $mon -= 12; $year++; }
-
- } #foreach $which_month
-
- } #if $tax->exempt_amount
-
- $taxable_charged = sprintf( "%.2f", $taxable_charged);
-
- #$tax += $taxable_charged * $cust_main_county->tax / 100
- $tax{ $tax->taxname || 'Tax' } +=
- $taxable_charged * $tax->tax / 100
-
- } #foreach my $tax ( @taxes )
-
- } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
-
- } #if $setup != 0 || $recur != 0
-
- } #if $cust_pkg->modified
-
- } #foreach my $cust_pkg
-
- unless ( $cust_bill->cust_bill_pkg ) {
- $cust_bill->delete; #don't create an invoice w/o line items
-
- # XXX this seems to be broken
- #( DBD::Pg::st execute failed: ERROR: syntax error at or near "hcb" )
-# # get rid of our fake history too, waste of unecessary space
-# my $h_cleanup_query = q{
-# DELETE FROM h_cust_bill hcb
-# WHERE hcb.invnum = ?
-# AND NOT EXISTS ( SELECT 1 FROM cust_bill cb where cb.invnum = hcb.invnum )
-# };
-# my $h_sth = $dbh->prepare($h_cleanup_query);
-# $h_sth->execute($invnum);
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- return '';
- }
-
- my $charged = sprintf( "%.2f", $total_setup + $total_recur );
-
- foreach my $taxname ( grep { $tax{$_} > 0 } keys %tax ) {
- my $tax = sprintf("%.2f", $tax{$taxname} );
- $charged = sprintf( "%.2f", $charged+$tax );
-
- my $cust_bill_pkg = new FS::cust_bill_pkg ({
- 'invnum' => $invnum,
- 'pkgnum' => 0,
- 'setup' => $tax,
- 'recur' => 0,
- 'sdate' => '',
- 'edate' => '',
- 'itemdesc' => $taxname,
- });
- $error = $cust_bill_pkg->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't create invoice line item for invoice #$invnum: $error";
- }
- $total_setup += $tax;
-
- }
-
- $cust_bill->charged( sprintf( "%.2f", $total_setup + $total_recur ) );
- $error = $cust_bill->replace;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't update charged for invoice #$invnum: $error";
- }
-
- foreach my $hook ( @precommit_hooks ) {
- eval {
- &{$hook}; #($self) ?
- };
- if ( $@ ) {
- $dbh->rollback if $oldAutoCommit;
- return "$@ running precommit hook $hook\n";
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-}
-
-=item collect OPTIONS
-
-(Attempt to) collect money for this customer's outstanding invoices (see
-L<FS::cust_bill>). Usually used after the bill method.
-
-Actions are now triggered by billing events; see L<FS::part_event> and the
-billing events web interface. Old-style invoice events (see
-L<FS::part_bill_event>) have been deprecated.
-
-If there is an error, returns the error, otherwise returns false.
-
-Options are passed as name-value pairs.
-
-Currently available options are:
-
-=over 4
-
-=item invoice_time
-
-Use this time when deciding when to print invoices and late notices on those invoices. The default is now. It is specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item retry
-
-Retry card/echeck/LEC transactions even when not scheduled by invoice events.
-
-=item quiet
-
-set true to surpress email card/ACH decline notices.
-
-=item check_freq
-
-"1d" for the traditional, daily events (the default), or "1m" for the new monthly events (part_event.check_freq)
-
-=item payby
-
-allows for one time override of normal customer billing method
-
-=item debug
-
-Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
-
-
-=back
-
-=cut
-
-sub collect {
- my( $self, %options ) = @_;
- my $invoice_time = $options{'invoice_time'} || time;
-
- #put below somehow?
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $self->select_for_update; #mutex
-
- if ( $DEBUG ) {
- my $balance = $self->balance;
- warn "$me collect customer ". $self->custnum. ": balance $balance\n"
- }
-
- if ( exists($options{'retry_card'}) ) {
- carp 'retry_card option passed to collect is deprecated; use retry';
- $options{'retry'} ||= $options{'retry_card'};
- }
- if ( exists($options{'retry'}) && $options{'retry'} ) {
- my $error = $self->retry_realtime;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- # false laziness w/pay_batch::import_results
-
- my $due_cust_event = $self->due_cust_event(
- 'debug' => ( $options{'debug'} || 0 ),
- 'time' => $invoice_time,
- 'check_freq' => $options{'check_freq'},
- );
- unless( ref($due_cust_event) ) {
- $dbh->rollback if $oldAutoCommit;
- return $due_cust_event;
- }
-
- foreach my $cust_event ( @$due_cust_event ) {
-
- #XXX lock event
-
- #re-eval event conditions (a previous event could have changed things)
- unless ( $cust_event->test_conditions( 'time' => $invoice_time ) ) {
- #don't leave stray "new/locked" records around
- my $error = $cust_event->delete;
- if ( $error ) {
- #gah, even with transactions
- $dbh->commit if $oldAutoCommit; #well.
- return $error;
- }
- next;
- }
-
- {
- local $realtime_bop_decline_quiet = 1 if $options{'quiet'};
- warn " running cust_event ". $cust_event->eventnum. "\n"
- if $DEBUG > 1;
-
-
- #if ( my $error = $cust_event->do_event(%options) ) { #XXX %options?
- if ( my $error = $cust_event->do_event() ) {
- #XXX wtf is this? figure out a proper dealio with return value
- #from do_event
- # gah, even with transactions.
- $dbh->commit if $oldAutoCommit; #well.
- return $error;
- }
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item due_cust_event [ HASHREF | OPTION => VALUE ... ]
-
-Inserts database records for and returns an ordered listref of new events due
-for this customer, as FS::cust_event objects (see L<FS::cust_event>). If no
-events are due, an empty listref is returned. If there is an error, returns a
-scalar error message.
-
-To actually run the events, call each event's test_condition method, and if
-still true, call the event's do_event method.
-
-Options are passed as a hashref or as a list of name-value pairs. Available
-options are:
-
-=over 4
-
-=item check_freq
-
-Search only for events of this check frequency (how often events of this type are checked); currently "1d" (daily, the default) and "1m" (monthly) are recognized.
-
-=item time
-
-"Current time" for the events.
-
-=item debug
-
-Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
-
-=item eventtable
-
-Only return events for the specified eventtable (by default, events of all eventtables are returned)
-
-=item objects
-
-Explicitly pass the objects to be tested (typically used with eventtable).
-
-=back
-
-=cut
-
-sub due_cust_event {
- my $self = shift;
- my %opt = ref($_[0]) ? %{ $_[0] } : @_;
-
- #???
- #my $DEBUG = $opt{'debug'}
- local($DEBUG) = $opt{'debug'}
- if defined($opt{'debug'}) && $opt{'debug'} > $DEBUG;
-
- warn "$me due_cust_event called with options ".
- join(', ', map { "$_: $opt{$_}" } keys %opt). "\n"
- if $DEBUG;
-
- $opt{'time'} ||= time;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $self->select_for_update; #mutex
-
- ###
- # 1: find possible events (initial search)
- ###
-
- my @cust_event = ();
-
- my @eventtable = $opt{'eventtable'}
- ? ( $opt{'eventtable'} )
- : FS::part_event->eventtables_runorder;
-
- foreach my $eventtable ( @eventtable ) {
-
- my @objects;
- if ( $opt{'objects'} ) {
-
- @objects = @{ $opt{'objects'} };
-
- } else {
-
- #my @objects = $self->eventtable(); # sub cust_main { @{ [ $self ] }; }
- @objects = ( $eventtable eq 'cust_main' )
- ? ( $self )
- : ( $self->$eventtable() );
-
- }
-
- my @e_cust_event = ();
-
- my $cross = "CROSS JOIN $eventtable";
- $cross .= ' LEFT JOIN cust_main USING ( custnum )'
- unless $eventtable eq 'cust_main';
-
- foreach my $object ( @objects ) {
-
- #this first search uses the condition_sql magic for optimization.
- #the more possible events we can eliminate in this step the better
-
- my $cross_where = '';
- my $pkey = $object->primary_key;
- $cross_where = "$eventtable.$pkey = ". $object->$pkey();
-
- my $join = FS::part_event_condition->join_conditions_sql( $eventtable );
- my $extra_sql =
- FS::part_event_condition->where_conditions_sql( $eventtable,
- 'time'=>$opt{'time'}
- );
- my $order = FS::part_event_condition->order_conditions_sql( $eventtable );
-
- $extra_sql = "AND $extra_sql" if $extra_sql;
-
- #here is the agent virtualization
- $extra_sql .= " AND ( part_event.agentnum IS NULL
- OR part_event.agentnum = ". $self->agentnum. ' )';
-
- $extra_sql .= " $order";
-
- warn "searching for events for $eventtable ". $object->$pkey. "\n"
- if $opt{'debug'} > 2;
- my @part_event = qsearch( {
- 'debug' => ( $opt{'debug'} > 3 ? 1 : 0 ),
- 'select' => 'part_event.*',
- 'table' => 'part_event',
- 'addl_from' => "$cross $join",
- 'hashref' => { 'check_freq' => ( $opt{'check_freq'} || '1d' ),
- 'eventtable' => $eventtable,
- 'disabled' => '',
- },
- 'extra_sql' => "AND $cross_where $extra_sql",
- } );
-
- if ( $DEBUG > 2 ) {
- my $pkey = $object->primary_key;
- warn " ". scalar(@part_event).
- " possible events found for $eventtable ". $object->$pkey(). "\n";
- }
-
- push @e_cust_event, map { $_->new_cust_event($object) } @part_event;
-
- }
-
- warn " ". scalar(@e_cust_event).
- " subtotal possible cust events found for $eventtable\n"
- if $DEBUG > 1;
-
- push @cust_event, @e_cust_event;
-
- }
-
- warn " ". scalar(@cust_event).
- " total possible cust events found in initial search\n"
- if $DEBUG; # > 1;
-
- ##
- # 2: test conditions
- ##
-
- my %unsat = ();
-
- @cust_event = grep $_->test_conditions( 'time' => $opt{'time'},
- 'stats_hashref' => \%unsat ),
- @cust_event;
-
- warn " ". scalar(@cust_event). " cust events left satisfying conditions\n"
- if $DEBUG; # > 1;
-
- warn " invalid conditions not eliminated with condition_sql:\n".
- join('', map " $_: ".$unsat{$_}."\n", keys %unsat )
- if $DEBUG; # > 1;
-
- ##
- # 3: insert
- ##
-
- foreach my $cust_event ( @cust_event ) {
-
- my $error = $cust_event->insert();
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- ##
- # 4: return
- ##
-
- warn " returning events: ". Dumper(@cust_event). "\n"
- if $DEBUG > 2;
-
- \@cust_event;
-
-}
-
-=item retry_realtime
-
-Schedules realtime / batch credit card / electronic check / LEC billing
-events for for retry. Useful if card information has changed or manual
-retry is desired. The 'collect' method must be called to actually retry
-the transaction.
-
-Implementation details: For either this customer, or for each of this
-customer's open invoices, changes the status of the first "done" (with
-statustext error) realtime processing event to "failed".
-
-=cut
-
-sub retry_realtime {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- #a little false laziness w/due_cust_event (not too bad, really)
-
- my $join = FS::part_event_condition->join_conditions_sql;
- my $order = FS::part_event_condition->order_conditions_sql;
- my $mine =
- '( '
- . join ( ' OR ' , map {
- "( part_event.eventtable = " . dbh->quote($_)
- . " AND tablenum IN( SELECT " . dbdef->table($_)->primary_key . " from $_ where custnum = " . dbh->quote( $self->custnum ) . "))" ;
- } FS::part_event->eventtables)
- . ') ';
-
- #here is the agent virtualization
- my $agent_virt = " ( part_event.agentnum IS NULL
- OR part_event.agentnum = ". $self->agentnum. ' )';
-
- #XXX this shouldn't be hardcoded, actions should declare it...
- my @realtime_events = qw(
- cust_bill_realtime_card
- cust_bill_realtime_check
- cust_bill_realtime_lec
- cust_bill_batch
- );
-
- my $is_realtime_event = ' ( '. join(' OR ', map "part_event.action = '$_'",
- @realtime_events
- ).
- ' ) ';
-
- my @cust_event = qsearchs({
- 'table' => 'cust_event',
- 'select' => 'cust_event.*',
- 'addl_from' => "LEFT JOIN part_event USING ( eventpart ) $join",
- 'hashref' => { 'status' => 'done' },
- 'extra_sql' => " AND statustext IS NOT NULL AND statustext != '' ".
- " AND $mine AND $is_realtime_event AND $agent_virt $order" # LIMIT 1"
- });
-
- my %seen_invnum = ();
- foreach my $cust_event (@cust_event) {
-
- #max one for the customer, one for each open invoice
- my $cust_X = $cust_event->cust_X;
- next if $seen_invnum{ $cust_event->part_event->eventtable eq 'cust_bill'
- ? $cust_X->invnum
- : 0
- }++
- or $cust_event->part_event->eventtable eq 'cust_bill'
- && ! $cust_X->owed;
-
- my $error = $cust_event->retry;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error scheduling event for retry: $error";
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item realtime_bop METHOD AMOUNT [ OPTION => VALUE ... ]
-
-Runs a realtime credit card, ACH (electronic check) or phone bill transaction
-via a Business::OnlinePayment realtime gateway. See
-L<http://420.am/business-onlinepayment> for supported gateways.
-
-Available methods are: I<CC>, I<ECHECK> and I<LEC>
-
-Available options are: I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>
-
-The additional options I<payname>, I<address1>, I<address2>, I<city>, I<state>,
-I<zip>, I<payinfo> and I<paydate> are also available. Any of these options,
-if set, will override the value from the customer record.
-
-I<description> is a free-text field passed to the gateway. It defaults to
-"Internet services".
-
-If an I<invnum> is specified, this payment (if successful) is applied to the
-specified invoice. If you don't specify an I<invnum> you might want to
-call the B<apply_payments> method.
-
-I<quiet> can be set true to surpress email decline notices.
-
-I<paynum_ref> can be set to a scalar reference. It will be filled in with the
-resulting paynum, if any.
-
-I<payunique> is a unique identifier for this payment.
-
-(moved from cust_bill) (probably should get realtime_{card,ach,lec} here too)
-
-=cut
-
-sub realtime_bop {
- my( $self, $method, $amount, %options ) = @_;
- if ( $DEBUG ) {
- warn "$me realtime_bop: $method $amount\n";
- warn " $_ => $options{$_}\n" foreach keys %options;
- }
-
- $options{'description'} ||= 'Internet services';
-
- return $self->fake_bop($method, $amount, %options) if $options{'fake'};
-
- eval "use Business::OnlinePayment";
- die $@ if $@;
-
- my $payinfo = exists($options{'payinfo'})
- ? $options{'payinfo'}
- : $self->payinfo;
-
- my %method2payby = (
- 'CC' => 'CARD',
- 'ECHECK' => 'CHEK',
- 'LEC' => 'LECB',
- );
-
- ###
- # check for banned credit card/ACH
- ###
-
- my $ban = qsearchs('banned_pay', {
- 'payby' => $method2payby{$method},
- 'payinfo' => md5_base64($payinfo),
- } );
- return "Banned credit card" if $ban;
-
- ###
- # select a gateway
- ###
-
- my $taxclass = '';
- if ( $options{'invnum'} ) {
- my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{'invnum'} } );
- die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
- my @taxclasses =
- map { $_->part_pkg->taxclass }
- grep { $_ }
- map { $_->cust_pkg }
- $cust_bill->cust_bill_pkg;
- unless ( grep { $taxclasses[0] ne $_ } @taxclasses ) { #unless there are
- #different taxclasses
- $taxclass = $taxclasses[0];
- }
- }
-
- #look for an agent gateway override first
- my $cardtype;
- if ( $method eq 'CC' ) {
- $cardtype = cardtype($payinfo);
- } elsif ( $method eq 'ECHECK' ) {
- $cardtype = 'ACH';
- } else {
- $cardtype = $method;
- }
-
- my $override =
- qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => $cardtype,
- taxclass => $taxclass, } )
- || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => '',
- taxclass => $taxclass, } )
- || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => $cardtype,
- taxclass => '', } )
- || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => '',
- taxclass => '', } );
-
- my $payment_gateway = '';
- my( $processor, $login, $password, $action, @bop_options );
- if ( $override ) { #use a payment gateway override
-
- $payment_gateway = $override->payment_gateway;
-
- $processor = $payment_gateway->gateway_module;
- $login = $payment_gateway->gateway_username;
- $password = $payment_gateway->gateway_password;
- $action = $payment_gateway->gateway_action;
- @bop_options = $payment_gateway->options;
-
- } else { #use the standard settings from the config
-
- ( $processor, $login, $password, $action, @bop_options ) =
- $self->default_payment_gateway($method);
-
- }
-
- ###
- # massage data
- ###
-
- my $address = exists($options{'address1'})
- ? $options{'address1'}
- : $self->address1;
- my $address2 = exists($options{'address2'})
- ? $options{'address2'}
- : $self->address2;
- $address .= ", ". $address2 if length($address2);
-
- my $o_payname = exists($options{'payname'})
- ? $options{'payname'}
- : $self->payname;
- my($payname, $payfirst, $paylast);
- if ( $o_payname && $method ne 'ECHECK' ) {
- ($payname = $o_payname) =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
- or return "Illegal payname $payname";
- ($payfirst, $paylast) = ($1, $2);
- } else {
- $payfirst = $self->getfield('first');
- $paylast = $self->getfield('last');
- $payname = "$payfirst $paylast";
- }
-
- my @invoicing_list = $self->invoicing_list_emailonly;
- if ( $conf->exists('emailinvoiceautoalways')
- || $conf->exists('emailinvoiceauto') && ! @invoicing_list
- || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
- push @invoicing_list, $self->all_emails;
- }
-
- my $email = ($conf->exists('business-onlinepayment-email-override'))
- ? $conf->config('business-onlinepayment-email-override')
- : $invoicing_list[0];
-
- my %content = ();
-
- my $payip = exists($options{'payip'})
- ? $options{'payip'}
- : $self->payip;
- $content{customer_ip} = $payip
- if length($payip);
-
- $content{invoice_number} = $options{'invnum'}
- if exists($options{'invnum'}) && length($options{'invnum'});
-
- $content{email_customer} =
- ( $conf->exists('business-onlinepayment-email_customer')
- || $conf->exists('business-onlinepayment-email-override') );
-
- my $paydate = '';
- if ( $method eq 'CC' ) {
-
- $content{card_number} = $payinfo;
- $paydate = exists($options{'paydate'})
- ? $options{'paydate'}
- : $self->paydate;
- $paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
- $content{expiration} = "$2/$1";
-
- my $paycvv = exists($options{'paycvv'})
- ? $options{'paycvv'}
- : $self->paycvv;
- $content{cvv2} = $paycvv
- if length($paycvv);
-
- my $paystart_month = exists($options{'paystart_month'})
- ? $options{'paystart_month'}
- : $self->paystart_month;
-
- my $paystart_year = exists($options{'paystart_year'})
- ? $options{'paystart_year'}
- : $self->paystart_year;
-
- $content{card_start} = "$paystart_month/$paystart_year"
- if $paystart_month && $paystart_year;
-
- my $payissue = exists($options{'payissue'})
- ? $options{'payissue'}
- : $self->payissue;
- $content{issue_number} = $payissue if $payissue;
-
- $content{recurring_billing} = 'YES'
- if qsearch('cust_pay', { 'custnum' => $self->custnum,
- 'payby' => 'CARD',
- 'payinfo' => $payinfo,
- } )
- || qsearch('cust_pay', { 'custnum' => $self->custnum,
- 'payby' => 'CARD',
- 'paymask' => $self->mask_payinfo('CARD', $payinfo),
- } );
-
-
- } elsif ( $method eq 'ECHECK' ) {
- ( $content{account_number}, $content{routing_code} ) =
- split('@', $payinfo);
- $content{bank_name} = $o_payname;
- $content{bank_state} = exists($options{'paystate'})
- ? $options{'paystate'}
- : $self->getfield('paystate');
- $content{account_type} = exists($options{'paytype'})
- ? uc($options{'paytype'}) || 'CHECKING'
- : uc($self->getfield('paytype')) || 'CHECKING';
- $content{account_name} = $payname;
- $content{customer_org} = $self->company ? 'B' : 'I';
- $content{state_id} = exists($options{'stateid'})
- ? $options{'stateid'}
- : $self->getfield('stateid');
- $content{state_id_state} = exists($options{'stateid_state'})
- ? $options{'stateid_state'}
- : $self->getfield('stateid_state');
- $content{customer_ssn} = exists($options{'ss'})
- ? $options{'ss'}
- : $self->ss;
- } elsif ( $method eq 'LEC' ) {
- $content{phone} = $payinfo;
- }
-
- ###
- # run transaction(s)
- ###
-
- my $balance = exists( $options{'balance'} )
- ? $options{'balance'}
- : $self->balance;
-
- $self->select_for_update; #mutex ... just until we get our pending record in
-
- #the checks here are intended to catch concurrent payments
- #double-form-submission prevention is taken care of in cust_pay_pending::check
-
- #check the balance
- return "The customer's balance has changed; $method transaction aborted."
- if $self->balance < $balance;
- #&& $self->balance < $amount; #might as well anyway?
-
- #also check and make sure there aren't *other* pending payments for this cust
-
- my @pending = qsearch('cust_pay_pending', {
- 'custnum' => $self->custnum,
- 'status' => { op=>'!=', value=>'done' }
- });
- return "A payment is already being processed for this customer (".
- join(', ', map 'paypendingnum '. $_->paypendingnum, @pending ).
- "); $method transaction aborted."
- if scalar(@pending);
-
- #okay, good to go, if we're a duplicate, cust_pay_pending will kick us out
-
- my $cust_pay_pending = new FS::cust_pay_pending {
- 'custnum' => $self->custnum,
- #'invnum' => $options{'invnum'},
- 'paid' => $amount,
- '_date' => '',
- 'payby' => $method2payby{$method},
- 'payinfo' => $payinfo,
- 'paydate' => $paydate,
- 'status' => 'new',
- 'gatewaynum' => ( $payment_gateway ? $payment_gateway->gatewaynum : '' ),
- };
- $cust_pay_pending->payunique( $options{payunique} )
- if defined($options{payunique}) && length($options{payunique});
- my $cpp_new_err = $cust_pay_pending->insert; #mutex lost when this is inserted
- return $cpp_new_err if $cpp_new_err;
-
- my( $action1, $action2 ) = split(/\s*\,\s*/, $action );
-
- my $transaction = new Business::OnlinePayment( $processor, @bop_options );
- $transaction->content(
- 'type' => $method,
- 'login' => $login,
- 'password' => $password,
- 'action' => $action1,
- 'description' => $options{'description'},
- 'amount' => $amount,
- #'invoice_number' => $options{'invnum'},
- 'customer_id' => $self->custnum,
- 'last_name' => $paylast,
- 'first_name' => $payfirst,
- 'name' => $payname,
- 'address' => $address,
- 'city' => ( exists($options{'city'})
- ? $options{'city'}
- : $self->city ),
- 'state' => ( exists($options{'state'})
- ? $options{'state'}
- : $self->state ),
- 'zip' => ( exists($options{'zip'})
- ? $options{'zip'}
- : $self->zip ),
- 'country' => ( exists($options{'country'})
- ? $options{'country'}
- : $self->country ),
- 'referer' => 'http://cleanwhisker.420.am/',
- 'email' => $email,
- 'phone' => $self->daytime || $self->night,
- %content, #after
- );
-
- $cust_pay_pending->status('pending');
- my $cpp_pending_err = $cust_pay_pending->replace;
- return $cpp_pending_err if $cpp_pending_err;
-
- #config?
- my $BOP_TESTING = 0;
- my $BOP_TESTING_SUCCESS = 1;
-
- unless ( $BOP_TESTING ) {
- $transaction->submit();
- } else {
- if ( $BOP_TESTING_SUCCESS ) {
- $transaction->is_success(1);
- $transaction->authorization('fake auth');
- } else {
- $transaction->is_success(0);
- $transaction->error_message('fake failure');
- }
- }
-
- if ( $transaction->is_success() && $action2 ) {
-
- $cust_pay_pending->status('authorized');
- my $cpp_authorized_err = $cust_pay_pending->replace;
- return $cpp_authorized_err if $cpp_authorized_err;
-
- my $auth = $transaction->authorization;
- my $ordernum = $transaction->can('order_number')
- ? $transaction->order_number
- : '';
-
- my $capture =
- new Business::OnlinePayment( $processor, @bop_options );
-
- my %capture = (
- %content,
- type => $method,
- action => $action2,
- login => $login,
- password => $password,
- order_number => $ordernum,
- amount => $amount,
- authorization => $auth,
- description => $options{'description'},
- );
-
- foreach my $field (qw( authorization_source_code returned_ACI
- transaction_identifier validation_code
- transaction_sequence_num local_transaction_date
- local_transaction_time AVS_result_code )) {
- $capture{$field} = $transaction->$field() if $transaction->can($field);
- }
-
- $capture->content( %capture );
-
- $capture->submit();
-
- unless ( $capture->is_success ) {
- my $e = "Authorization successful but capture failed, custnum #".
- $self->custnum. ': '. $capture->result_code.
- ": ". $capture->error_message;
- warn $e;
- return $e;
- }
-
- }
-
- $cust_pay_pending->status($transaction->is_success() ? 'captured' : 'declined');
- my $cpp_captured_err = $cust_pay_pending->replace;
- return $cpp_captured_err if $cpp_captured_err;
-
- ###
- # remove paycvv after initial transaction
- ###
-
- #false laziness w/misc/process/payment.cgi - check both to make sure working
- # correctly
- if ( defined $self->dbdef_table->column('paycvv')
- && length($self->paycvv)
- && ! grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save')
- ) {
- my $error = $self->remove_cvv;
- if ( $error ) {
- warn "WARNING: error removing cvv: $error\n";
- }
- }
-
- ###
- # result handling
- ###
-
- if ( $transaction->is_success() ) {
-
- my $paybatch = '';
- if ( $payment_gateway ) { # agent override
- $paybatch = $payment_gateway->gatewaynum. '-';
- }
-
- $paybatch .= "$processor:". $transaction->authorization;
-
- $paybatch .= ':'. $transaction->order_number
- if $transaction->can('order_number')
- && length($transaction->order_number);
-
- my $cust_pay = new FS::cust_pay ( {
- 'custnum' => $self->custnum,
- 'invnum' => $options{'invnum'},
- 'paid' => $amount,
- '_date' => '',
- 'payby' => $method2payby{$method},
- 'payinfo' => $payinfo,
- 'paybatch' => $paybatch,
- 'paydate' => $paydate,
- } );
- #doesn't hurt to know, even though the dup check is in cust_pay_pending now
- $cust_pay->payunique( $options{payunique} )
- if defined($options{payunique}) && length($options{payunique});
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- #start a transaction, insert the cust_pay and set cust_pay_pending.status to done in a single transction
-
- my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
-
- if ( $error ) {
- $cust_pay->invnum(''); #try again with no specific invnum
- my $error2 = $cust_pay->insert( $options{'manual'} ?
- ( 'manual' => 1 ) : ()
- );
- if ( $error2 ) {
- # gah. but at least we have a record of the state we had to abort in
- # from cust_pay_pending now.
- my $e = "WARNING: $method captured but payment not recorded - ".
- "error inserting payment ($processor): $error2".
- " (previously tried insert with invnum #$options{'invnum'}" .
- ": $error ) - pending payment saved as paypendingnum ".
- $cust_pay_pending->paypendingnum. "\n";
- warn $e;
- return $e;
- }
- }
-
- if ( $options{'paynum_ref'} ) {
- ${ $options{'paynum_ref'} } = $cust_pay->paynum;
- }
-
- $cust_pay_pending->status('done');
- $cust_pay_pending->statustext('captured');
- my $cpp_done_err = $cust_pay_pending->replace;
-
- if ( $cpp_done_err ) {
-
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- my $e = "WARNING: $method captured but payment not recorded - ".
- "error updating status for paypendingnum ".
- $cust_pay_pending->paypendingnum. ": $cpp_done_err \n";
- warn $e;
- return $e;
-
- } else {
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- return ''; #no error
-
- }
-
- } else {
-
- my $perror = "$processor error: ". $transaction->error_message;
-
- unless ( $transaction->error_message ) {
-
- my $t_response;
- if ( $transaction->can('response_page') ) {
- $t_response = {
- 'page' => ( $transaction->can('response_page')
- ? $transaction->response_page
- : ''
- ),
- 'code' => ( $transaction->can('response_code')
- ? $transaction->response_code
- : ''
- ),
- 'headers' => ( $transaction->can('response_headers')
- ? $transaction->response_headers
- : ''
- ),
- };
- } else {
- $t_response .=
- "No additional debugging information available for $processor";
- }
-
- $perror .= "No error_message returned from $processor -- ".
- ( ref($t_response) ? Dumper($t_response) : $t_response );
-
- }
-
- if ( !$options{'quiet'} && !$realtime_bop_decline_quiet
- && $conf->exists('emaildecline')
- && grep { $_ ne 'POST' } $self->invoicing_list
- && ! grep { $transaction->error_message =~ /$_/ }
- $conf->config('emaildecline-exclude')
- ) {
- my @templ = $conf->config('declinetemplate');
- my $template = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", @templ ],
- ) or return "($perror) can't create template: $Text::Template::ERROR";
- $template->compile()
- or return "($perror) can't compile template: $Text::Template::ERROR";
-
- my $templ_hash = { error => $transaction->error_message };
-
- my $error = send_email(
- 'from' => $conf->config('invoice_from'),
- 'to' => [ grep { $_ ne 'POST' } $self->invoicing_list ],
- 'subject' => 'Your payment could not be processed',
- 'body' => [ $template->fill_in(HASH => $templ_hash) ],
- );
-
- $perror .= " (also received error sending decline notification: $error)"
- if $error;
-
- }
-
- $cust_pay_pending->status('done');
- $cust_pay_pending->statustext("declined: $perror");
- my $cpp_done_err = $cust_pay_pending->replace;
- if ( $cpp_done_err ) {
- my $e = "WARNING: $method declined but pending payment not resolved - ".
- "error updating status for paypendingnum ".
- $cust_pay_pending->paypendingnum. ": $cpp_done_err \n";
- warn $e;
- $perror = "$e ($perror)";
- }
-
- return $perror;
- }
-
-}
-
-=item fake_bop
-
-=cut
-
-sub fake_bop {
- my( $self, $method, $amount, %options ) = @_;
-
- if ( $options{'fake_failure'} ) {
- return "Error: No error; test failure requested with fake_failure";
- }
-
- my %method2payby = (
- 'CC' => 'CARD',
- 'ECHECK' => 'CHEK',
- 'LEC' => 'LECB',
- );
-
- #my $paybatch = '';
- #if ( $payment_gateway ) { # agent override
- # $paybatch = $payment_gateway->gatewaynum. '-';
- #}
- #
- #$paybatch .= "$processor:". $transaction->authorization;
- #
- #$paybatch .= ':'. $transaction->order_number
- # if $transaction->can('order_number')
- # && length($transaction->order_number);
-
- my $paybatch = 'FakeProcessor:54:32';
-
- my $cust_pay = new FS::cust_pay ( {
- 'custnum' => $self->custnum,
- 'invnum' => $options{'invnum'},
- 'paid' => $amount,
- '_date' => '',
- 'payby' => $method2payby{$method},
- #'payinfo' => $payinfo,
- 'payinfo' => '4111111111111111',
- 'paybatch' => $paybatch,
- #'paydate' => $paydate,
- 'paydate' => '2012-05-01',
- } );
- $cust_pay->payunique( $options{payunique} ) if length($options{payunique});
-
- my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
-
- if ( $error ) {
- $cust_pay->invnum(''); #try again with no specific invnum
- my $error2 = $cust_pay->insert( $options{'manual'} ?
- ( 'manual' => 1 ) : ()
- );
- if ( $error2 ) {
- # gah, even with transactions.
- my $e = 'WARNING: Card/ACH debited but database not updated - '.
- "error inserting (fake!) payment: $error2".
- " (previously tried insert with invnum #$options{'invnum'}" .
- ": $error )";
- warn $e;
- return $e;
- }
- }
-
- if ( $options{'paynum_ref'} ) {
- ${ $options{'paynum_ref'} } = $cust_pay->paynum;
- }
-
- return ''; #no error
-
-}
-
-=item default_payment_gateway
-
-=cut
-
-sub default_payment_gateway {
- my( $self, $method ) = @_;
-
- die "Real-time processing not enabled\n"
- unless $conf->exists('business-onlinepayment');
-
- #load up config
- my $bop_config = 'business-onlinepayment';
- $bop_config .= '-ach'
- if $method =~ /^(ECHECK|CHEK)$/ && $conf->exists($bop_config. '-ach');
- my ( $processor, $login, $password, $action, @bop_options ) =
- $conf->config($bop_config);
- $action ||= 'normal authorization';
- pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
- die "No real-time processor is enabled - ".
- "did you set the business-onlinepayment configuration value?\n"
- unless $processor;
-
- ( $processor, $login, $password, $action, @bop_options )
-}
-
-=item remove_cvv
-
-Removes the I<paycvv> field from the database directly.
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub remove_cvv {
- my $self = shift;
- my $sth = dbh->prepare("UPDATE cust_main SET paycvv = '' WHERE custnum = ?")
- or return dbh->errstr;
- $sth->execute($self->custnum)
- or return $sth->errstr;
- $self->paycvv('');
- '';
-}
-
-=item realtime_refund_bop METHOD [ OPTION => VALUE ... ]
-
-Refunds a realtime credit card, ACH (electronic check) or phone bill transaction
-via a Business::OnlinePayment realtime gateway. See
-L<http://420.am/business-onlinepayment> for supported gateways.
-
-Available methods are: I<CC>, I<ECHECK> and I<LEC>
-
-Available options are: I<amount>, I<reason>, I<paynum>, I<paydate>
-
-Most gateways require a reference to an original payment transaction to refund,
-so you probably need to specify a I<paynum>.
-
-I<amount> defaults to the original amount of the payment if not specified.
-
-I<reason> specifies a reason for the refund.
-
-I<paydate> specifies the expiration date for a credit card overriding the
-value from the customer record or the payment record. Specified as yyyy-mm-dd
-
-Implementation note: If I<amount> is unspecified or equal to the amount of the
-orignal payment, first an attempt is made to "void" the transaction via
-the gateway (to cancel a not-yet settled transaction) and then if that fails,
-the normal attempt is made to "refund" ("credit") the transaction via the
-gateway is attempted.
-
-#The additional options I<payname>, I<address1>, I<address2>, I<city>, I<state>,
-#I<zip>, I<payinfo> and I<paydate> are also available. Any of these options,
-#if set, will override the value from the customer record.
-
-#If an I<invnum> is specified, this payment (if successful) is applied to the
-#specified invoice. If you don't specify an I<invnum> you might want to
-#call the B<apply_payments> method.
-
-=cut
-
-#some false laziness w/realtime_bop, not enough to make it worth merging
-#but some useful small subs should be pulled out
-sub realtime_refund_bop {
- my( $self, $method, %options ) = @_;
- if ( $DEBUG ) {
- warn "$me realtime_refund_bop: $method refund\n";
- warn " $_ => $options{$_}\n" foreach keys %options;
- }
-
- eval "use Business::OnlinePayment";
- die $@ if $@;
-
- ###
- # look up the original payment and optionally a gateway for that payment
- ###
-
- my $cust_pay = '';
- my $amount = $options{'amount'};
-
- my( $processor, $login, $password, @bop_options ) ;
- my( $auth, $order_number ) = ( '', '', '' );
-
- if ( $options{'paynum'} ) {
-
- warn " paynum: $options{paynum}\n" if $DEBUG > 1;
- $cust_pay = qsearchs('cust_pay', { paynum=>$options{'paynum'} } )
- or return "Unknown paynum $options{'paynum'}";
- $amount ||= $cust_pay->paid;
-
- $cust_pay->paybatch =~ /^((\d+)\-)?(\w+):\s*([\w\-\/ ]*)(:([\w\-]+))?$/
- or return "Can't parse paybatch for paynum $options{'paynum'}: ".
- $cust_pay->paybatch;
- my $gatewaynum = '';
- ( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 );
-
- if ( $gatewaynum ) { #gateway for the payment to be refunded
-
- my $payment_gateway =
- qsearchs('payment_gateway', { 'gatewaynum' => $gatewaynum } );
- die "payment gateway $gatewaynum not found"
- unless $payment_gateway;
-
- $processor = $payment_gateway->gateway_module;
- $login = $payment_gateway->gateway_username;
- $password = $payment_gateway->gateway_password;
- @bop_options = $payment_gateway->options;
-
- } else { #try the default gateway
-
- my( $conf_processor, $unused_action );
- ( $conf_processor, $login, $password, $unused_action, @bop_options ) =
- $self->default_payment_gateway($method);
-
- return "processor of payment $options{'paynum'} $processor does not".
- " match default processor $conf_processor"
- unless $processor eq $conf_processor;
-
- }
-
-
- } else { # didn't specify a paynum, so look for agent gateway overrides
- # like a normal transaction
-
- my $cardtype;
- if ( $method eq 'CC' ) {
- $cardtype = cardtype($self->payinfo);
- } elsif ( $method eq 'ECHECK' ) {
- $cardtype = 'ACH';
- } else {
- $cardtype = $method;
- }
- my $override =
- qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => $cardtype,
- taxclass => '', } )
- || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => '',
- taxclass => '', } );
-
- if ( $override ) { #use a payment gateway override
-
- my $payment_gateway = $override->payment_gateway;
-
- $processor = $payment_gateway->gateway_module;
- $login = $payment_gateway->gateway_username;
- $password = $payment_gateway->gateway_password;
- #$action = $payment_gateway->gateway_action;
- @bop_options = $payment_gateway->options;
-
- } else { #use the standard settings from the config
-
- my $unused_action;
- ( $processor, $login, $password, $unused_action, @bop_options ) =
- $self->default_payment_gateway($method);
-
- }
-
- }
- return "neither amount nor paynum specified" unless $amount;
-
- my %content = (
- 'type' => $method,
- 'login' => $login,
- 'password' => $password,
- 'order_number' => $order_number,
- 'amount' => $amount,
- 'referer' => 'http://cleanwhisker.420.am/',
- );
- $content{authorization} = $auth
- if length($auth); #echeck/ACH transactions have an order # but no auth
- #(at least with authorize.net)
-
- my $disable_void_after;
- if ($conf->exists('disable_void_after')
- && $conf->config('disable_void_after') =~ /^(\d+)$/) {
- $disable_void_after = $1;
- }
-
- #first try void if applicable
- if ( $cust_pay && $cust_pay->paid == $amount
- && (
- ( not defined($disable_void_after) )
- || ( time < ($cust_pay->_date + $disable_void_after ) )
- )
- ) {
- warn " attempting void\n" if $DEBUG > 1;
- my $void = new Business::OnlinePayment( $processor, @bop_options );
- $void->content( 'action' => 'void', %content );
- $void->submit();
- if ( $void->is_success ) {
- my $error = $cust_pay->void($options{'reason'});
- if ( $error ) {
- # gah, even with transactions.
- my $e = 'WARNING: Card/ACH voided but database not updated - '.
- "error voiding payment: $error";
- warn $e;
- return $e;
- }
- warn " void successful\n" if $DEBUG > 1;
- return '';
- }
- }
-
- warn " void unsuccessful, trying refund\n"
- if $DEBUG > 1;
-
- #massage data
- my $address = $self->address1;
- $address .= ", ". $self->address2 if $self->address2;
-
- my($payname, $payfirst, $paylast);
- if ( $self->payname && $method ne 'ECHECK' ) {
- $payname = $self->payname;
- $payname =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
- or return "Illegal payname $payname";
- ($payfirst, $paylast) = ($1, $2);
- } else {
- $payfirst = $self->getfield('first');
- $paylast = $self->getfield('last');
- $payname = "$payfirst $paylast";
- }
-
- my @invoicing_list = $self->invoicing_list_emailonly;
- if ( $conf->exists('emailinvoiceautoalways')
- || $conf->exists('emailinvoiceauto') && ! @invoicing_list
- || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
- push @invoicing_list, $self->all_emails;
- }
-
- my $email = ($conf->exists('business-onlinepayment-email-override'))
- ? $conf->config('business-onlinepayment-email-override')
- : $invoicing_list[0];
-
- my $payip = exists($options{'payip'})
- ? $options{'payip'}
- : $self->payip;
- $content{customer_ip} = $payip
- if length($payip);
-
- my $payinfo = '';
- if ( $method eq 'CC' ) {
-
- if ( $cust_pay ) {
- $content{card_number} = $payinfo = $cust_pay->payinfo;
- (exists($options{'paydate'}) ? $options{'paydate'} : $cust_pay->paydate)
- =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/ &&
- ($content{expiration} = "$2/$1"); # where available
- } else {
- $content{card_number} = $payinfo = $self->payinfo;
- (exists($options{'paydate'}) ? $options{'paydate'} : $self->paydate)
- =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
- $content{expiration} = "$2/$1";
- }
-
- } elsif ( $method eq 'ECHECK' ) {
-
- if ( $cust_pay ) {
- $payinfo = $cust_pay->payinfo;
- } else {
- $payinfo = $self->payinfo;
- }
- ( $content{account_number}, $content{routing_code} )= split('@', $payinfo );
- $content{bank_name} = $self->payname;
- $content{account_type} = 'CHECKING';
- $content{account_name} = $payname;
- $content{customer_org} = $self->company ? 'B' : 'I';
- $content{customer_ssn} = $self->ss;
- } elsif ( $method eq 'LEC' ) {
- $content{phone} = $payinfo = $self->payinfo;
- }
-
- #then try refund
- my $refund = new Business::OnlinePayment( $processor, @bop_options );
- my %sub_content = $refund->content(
- 'action' => 'credit',
- 'customer_id' => $self->custnum,
- 'last_name' => $paylast,
- 'first_name' => $payfirst,
- 'name' => $payname,
- 'address' => $address,
- 'city' => $self->city,
- 'state' => $self->state,
- 'zip' => $self->zip,
- 'country' => $self->country,
- 'email' => $email,
- 'phone' => $self->daytime || $self->night,
- %content, #after
- );
- warn join('', map { " $_ => $sub_content{$_}\n" } keys %sub_content )
- if $DEBUG > 1;
- $refund->submit();
-
- return "$processor error: ". $refund->error_message
- unless $refund->is_success();
-
- my %method2payby = (
- 'CC' => 'CARD',
- 'ECHECK' => 'CHEK',
- 'LEC' => 'LECB',
- );
-
- my $paybatch = "$processor:". $refund->authorization;
- $paybatch .= ':'. $refund->order_number
- if $refund->can('order_number') && $refund->order_number;
-
- while ( $cust_pay && $cust_pay->unapplied < $amount ) {
- my @cust_bill_pay = $cust_pay->cust_bill_pay;
- last unless @cust_bill_pay;
- my $cust_bill_pay = pop @cust_bill_pay;
- my $error = $cust_bill_pay->delete;
- last if $error;
- }
-
- my $cust_refund = new FS::cust_refund ( {
- 'custnum' => $self->custnum,
- 'paynum' => $options{'paynum'},
- 'refund' => $amount,
- '_date' => '',
- 'payby' => $method2payby{$method},
- 'payinfo' => $payinfo,
- 'paybatch' => $paybatch,
- 'reason' => $options{'reason'} || 'card or ACH refund',
- } );
- my $error = $cust_refund->insert;
- if ( $error ) {
- $cust_refund->paynum(''); #try again with no specific paynum
- my $error2 = $cust_refund->insert;
- if ( $error2 ) {
- # gah, even with transactions.
- my $e = 'WARNING: Card/ACH refunded but database not updated - '.
- "error inserting refund ($processor): $error2".
- " (previously tried insert with paynum #$options{'paynum'}" .
- ": $error )";
- warn $e;
- return $e;
- }
- }
-
- ''; #no error
-
-}
-
-=item batch_card OPTION => VALUE...
-
-Adds a payment for this invoice to the pending credit card batch (see
-L<FS::cust_pay_batch>), or, if the B<realtime> option is set to a true value,
-runs the payment using a realtime gateway.
-
-=cut
-
-sub batch_card {
- my ($self, %options) = @_;
-
- my $amount;
- if (exists($options{amount})) {
- $amount = $options{amount};
- }else{
- $amount = sprintf("%.2f", $self->balance - $self->in_transit_payments);
- }
- return '' unless $amount > 0;
-
- my $invnum = delete $options{invnum};
- my $payby = $options{invnum} || $self->payby; #dubious
-
- if ($options{'realtime'}) {
- return $self->realtime_bop( FS::payby->payby2bop($self->payby),
- $amount,
- %options,
- );
- }
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- #this needs to handle mysql as well as Pg, like svc_acct.pm
- #(make it into a common function if folks need to do batching with mysql)
- $dbh->do("LOCK TABLE pay_batch IN SHARE ROW EXCLUSIVE MODE")
- or return "Cannot lock pay_batch: " . $dbh->errstr;
-
- my %pay_batch = (
- 'status' => 'O',
- 'payby' => FS::payby->payby2payment($payby),
- );
-
- my $pay_batch = qsearchs( 'pay_batch', \%pay_batch );
-
- unless ( $pay_batch ) {
- $pay_batch = new FS::pay_batch \%pay_batch;
- my $error = $pay_batch->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- die "error creating new batch: $error\n";
- }
- }
-
- my $old_cust_pay_batch = qsearchs('cust_pay_batch', {
- 'batchnum' => $pay_batch->batchnum,
- 'custnum' => $self->custnum,
- } );
-
- foreach (qw( address1 address2 city state zip country payby payinfo paydate
- payname )) {
- $options{$_} = '' unless exists($options{$_});
- }
-
- my $cust_pay_batch = new FS::cust_pay_batch ( {
- 'batchnum' => $pay_batch->batchnum,
- 'invnum' => $invnum || 0, # is there a better value?
- # this field should be
- # removed...
- # cust_bill_pay_batch now
- 'custnum' => $self->custnum,
- 'last' => $self->getfield('last'),
- 'first' => $self->getfield('first'),
- 'address1' => $options{address1} || $self->address1,
- 'address2' => $options{address2} || $self->address2,
- 'city' => $options{city} || $self->city,
- 'state' => $options{state} || $self->state,
- 'zip' => $options{zip} || $self->zip,
- 'country' => $options{country} || $self->country,
- 'payby' => $options{payby} || $self->payby,
- 'payinfo' => $options{payinfo} || $self->payinfo,
- 'exp' => $options{paydate} || $self->paydate,
- 'payname' => $options{payname} || $self->payname,
- 'amount' => $amount, # consolidating
- } );
-
- $cust_pay_batch->paybatchnum($old_cust_pay_batch->paybatchnum)
- if $old_cust_pay_batch;
-
- my $error;
- if ($old_cust_pay_batch) {
- $error = $cust_pay_batch->replace($old_cust_pay_batch)
- } else {
- $error = $cust_pay_batch->insert;
- }
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- die $error;
- }
-
- my $unapplied = $self->total_credited + $self->total_unapplied_payments + $self->in_transit_payments;
- foreach my $cust_bill ($self->open_cust_bill) {
- #$dbh->commit or die $dbh->errstr if $oldAutoCommit;
- my $cust_bill_pay_batch = new FS::cust_bill_pay_batch {
- 'invnum' => $cust_bill->invnum,
- 'paybatchnum' => $cust_pay_batch->paybatchnum,
- 'amount' => $cust_bill->owed,
- '_date' => time,
- };
- if ($unapplied >= $cust_bill_pay_batch->amount){
- $unapplied -= $cust_bill_pay_batch->amount;
- next;
- }else{
- $cust_bill_pay_batch->amount(sprintf ( "%.2f",
- $cust_bill_pay_batch->amount - $unapplied )); $unapplied = 0;
- }
- $error = $cust_bill_pay_batch->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- die $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-=item total_owed
-
-Returns the total owed for this customer on all invoices
-(see L<FS::cust_bill/owed>).
-
-=cut
-
-sub total_owed {
- my $self = shift;
- $self->total_owed_date(2145859200); #12/31/2037
-}
-
-=item total_owed_date TIME
-
-Returns the total owed for this customer on all invoices with date earlier than
-TIME. TIME is specified as a UNIX timestamp; see L<perlfunc/"time">). Also
-see L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-sub total_owed_date {
- my $self = shift;
- my $time = shift;
- my $total_bill = 0;
- foreach my $cust_bill (
- grep { $_->_date <= $time }
- qsearch('cust_bill', { 'custnum' => $self->custnum, } )
- ) {
- $total_bill += $cust_bill->owed;
- }
- sprintf( "%.2f", $total_bill );
-}
-
-=item apply_payments_and_credits
-
-Applies unapplied payments and credits.
-
-In most cases, this new method should be used in place of sequential
-apply_payments and apply_credits methods.
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub apply_payments_and_credits {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $self->select_for_update; #mutex
-
- foreach my $cust_bill ( $self->open_cust_bill ) {
- my $error = $cust_bill->apply_payments_and_credits;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error applying: $error";
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-
-}
-
-=item apply_credits OPTION => VALUE ...
-
-Applies (see L<FS::cust_credit_bill>) unapplied credits (see L<FS::cust_credit>)
-to outstanding invoice balances in chronological order (or reverse
-chronological order if the I<order> option is set to B<newest>) and returns the
-value of any remaining unapplied credits available for refund (see
-L<FS::cust_refund>).
-
-Dies if there is an error.
-
-=cut
-
-sub apply_credits {
- my $self = shift;
- my %opt = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $self->select_for_update; #mutex
-
- unless ( $self->total_credited ) {
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- return 0;
- }
-
- my @credits = sort { $b->_date <=> $a->_date} (grep { $_->credited > 0 }
- qsearch('cust_credit', { 'custnum' => $self->custnum } ) );
-
- my @invoices = $self->open_cust_bill;
- @invoices = sort { $b->_date <=> $a->_date } @invoices
- if defined($opt{'order'}) && $opt{'order'} eq 'newest';
-
- my $credit;
- foreach my $cust_bill ( @invoices ) {
- my $amount;
-
- if ( !defined($credit) || $credit->credited == 0) {
- $credit = pop @credits or last;
- }
-
- if ($cust_bill->owed >= $credit->credited) {
- $amount=$credit->credited;
- }else{
- $amount=$cust_bill->owed;
- }
-
- my $cust_credit_bill = new FS::cust_credit_bill ( {
- 'crednum' => $credit->crednum,
- 'invnum' => $cust_bill->invnum,
- 'amount' => $amount,
- } );
- my $error = $cust_credit_bill->insert;
- if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- die $error;
- }
-
- redo if ($cust_bill->owed > 0);
-
- }
-
- my $total_credited = $self->total_credited;
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- return $total_credited;
-}
-
-=item apply_payments
-
-Applies (see L<FS::cust_bill_pay>) unapplied payments (see L<FS::cust_pay>)
-to outstanding invoice balances in chronological order.
-
- #and returns the value of any remaining unapplied payments.
-
-Dies if there is an error.
-
-=cut
-
-sub apply_payments {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $self->select_for_update; #mutex
-
- #return 0 unless
-
- my @payments = sort { $b->_date <=> $a->_date } ( grep { $_->unapplied > 0 }
- qsearch('cust_pay', { 'custnum' => $self->custnum } ) );
-
- my @invoices = sort { $a->_date <=> $b->_date} (grep { $_->owed > 0 }
- qsearch('cust_bill', { 'custnum' => $self->custnum } ) );
-
- my $payment;
-
- foreach my $cust_bill ( @invoices ) {
- my $amount;
-
- if ( !defined($payment) || $payment->unapplied == 0 ) {
- $payment = pop @payments or last;
- }
-
- if ( $cust_bill->owed >= $payment->unapplied ) {
- $amount = $payment->unapplied;
- } else {
- $amount = $cust_bill->owed;
- }
-
- my $cust_bill_pay = new FS::cust_bill_pay ( {
- 'paynum' => $payment->paynum,
- 'invnum' => $cust_bill->invnum,
- 'amount' => $amount,
- } );
- my $error = $cust_bill_pay->insert;
- if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- die $error;
- }
-
- redo if ( $cust_bill->owed > 0);
-
- }
-
- my $total_unapplied_payments = $self->total_unapplied_payments;
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- return $total_unapplied_payments;
-}
-
-=item total_credited
-
-Returns the total outstanding credit (see L<FS::cust_credit>) for this
-customer. See L<FS::cust_credit/credited>.
-
-=cut
-
-sub total_credited {
- my $self = shift;
- my $total_credit = 0;
- foreach my $cust_credit ( qsearch('cust_credit', {
- 'custnum' => $self->custnum,
- } ) ) {
- $total_credit += $cust_credit->credited;
- }
- sprintf( "%.2f", $total_credit );
-}
-
-=item total_unapplied_payments
-
-Returns the total unapplied payments (see L<FS::cust_pay>) for this customer.
-See L<FS::cust_pay/unapplied>.
-
-=cut
-
-sub total_unapplied_payments {
- my $self = shift;
- my $total_unapplied = 0;
- foreach my $cust_pay ( qsearch('cust_pay', {
- 'custnum' => $self->custnum,
- } ) ) {
- $total_unapplied += $cust_pay->unapplied;
- }
- sprintf( "%.2f", $total_unapplied );
-}
-
-=item total_unapplied_refunds
-
-Returns the total unrefunded refunds (see L<FS::cust_refund>) for this
-customer. See L<FS::cust_refund/unapplied>.
-
-=cut
-
-sub total_unapplied_refunds {
- my $self = shift;
- my $total_unapplied = 0;
- foreach my $cust_refund ( qsearch('cust_refund', {
- 'custnum' => $self->custnum,
- } ) ) {
- $total_unapplied += $cust_refund->unapplied;
- }
- sprintf( "%.2f", $total_unapplied );
-}
-
-=item balance
-
-Returns the balance for this customer (total_owed plus total_unrefunded, minus
-total_credited minus total_unapplied_payments).
-
-=cut
-
-sub balance {
- my $self = shift;
- sprintf( "%.2f",
- $self->total_owed
- + $self->total_unapplied_refunds
- - $self->total_credited
- - $self->total_unapplied_payments
- );
-}
-
-=item balance_date TIME
-
-Returns the balance for this customer, only considering invoices with date
-earlier than TIME (total_owed_date minus total_credited minus
-total_unapplied_payments). TIME is specified as a UNIX timestamp; see
-L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-=cut
-
-sub balance_date {
- my $self = shift;
- my $time = shift;
- sprintf( "%.2f",
- $self->total_owed_date($time)
- + $self->total_unapplied_refunds
- - $self->total_credited
- - $self->total_unapplied_payments
- );
-}
-
-=item in_transit_payments
-
-Returns the total of requests for payments for this customer pending in
-batches in transit to the bank. See L<FS::pay_batch> and L<FS::cust_pay_batch>
-
-=cut
-
-sub in_transit_payments {
- my $self = shift;
- my $in_transit_payments = 0;
- foreach my $pay_batch ( qsearch('pay_batch', {
- 'status' => 'I',
- } ) ) {
- foreach my $cust_pay_batch ( qsearch('cust_pay_batch', {
- 'batchnum' => $pay_batch->batchnum,
- 'custnum' => $self->custnum,
- } ) ) {
- $in_transit_payments += $cust_pay_batch->amount;
- }
- }
- sprintf( "%.2f", $in_transit_payments );
-}
-
-=item paydate_monthyear
-
-Returns a two-element list consisting of the month and year of this customer's
-paydate (credit card expiration date for CARD customers)
-
-=cut
-
-sub paydate_monthyear {
- my $self = shift;
- if ( $self->paydate =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #Pg date format
- ( $2, $1 );
- } elsif ( $self->paydate =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
- ( $1, $3 );
- } else {
- ('', '');
- }
-}
-
-=item invoicing_list [ ARRAYREF ]
-
-If an arguement is given, sets these email addresses as invoice recipients
-(see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
-(except as warnings), so use check_invoicing_list first.
-
-Returns a list of email addresses (with svcnum entries expanded).
-
-Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
-check it without disturbing anything by passing nothing.
-
-This interface may change in the future.
-
-=cut
-
-sub invoicing_list {
- my( $self, $arrayref ) = @_;
-
- if ( $arrayref ) {
- my @cust_main_invoice;
- if ( $self->custnum ) {
- @cust_main_invoice =
- qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
- } else {
- @cust_main_invoice = ();
- }
- foreach my $cust_main_invoice ( @cust_main_invoice ) {
- #warn $cust_main_invoice->destnum;
- unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
- #warn $cust_main_invoice->destnum;
- my $error = $cust_main_invoice->delete;
- warn $error if $error;
- }
- }
- if ( $self->custnum ) {
- @cust_main_invoice =
- qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
- } else {
- @cust_main_invoice = ();
- }
- my %seen = map { $_->address => 1 } @cust_main_invoice;
- foreach my $address ( @{$arrayref} ) {
- next if exists $seen{$address} && $seen{$address};
- $seen{$address} = 1;
- my $cust_main_invoice = new FS::cust_main_invoice ( {
- 'custnum' => $self->custnum,
- 'dest' => $address,
- } );
- my $error = $cust_main_invoice->insert;
- warn $error if $error;
- }
- }
-
- if ( $self->custnum ) {
- map { $_->address }
- qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
- } else {
- ();
- }
-
-}
-
-=item check_invoicing_list ARRAYREF
-
-Checks these arguements as valid input for the invoicing_list method. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub check_invoicing_list {
- my( $self, $arrayref ) = @_;
-
- foreach my $address ( @$arrayref ) {
-
- if ($address eq 'FAX' and $self->getfield('fax') eq '') {
- return 'Can\'t add FAX invoice destination with a blank FAX number.';
- }
-
- my $cust_main_invoice = new FS::cust_main_invoice ( {
- 'custnum' => $self->custnum,
- 'dest' => $address,
- } );
- my $error = $self->custnum
- ? $cust_main_invoice->check
- : $cust_main_invoice->checkdest
- ;
- return $error if $error;
-
- }
-
- return "Email address required"
- if $conf->exists('cust_main-require_invoicing_list_email')
- && ! grep { $_ !~ /^([A-Z]+)$/ } @$arrayref;
-
- '';
-}
-
-=item set_default_invoicing_list
-
-Sets the invoicing list to all accounts associated with this customer,
-overwriting any previous invoicing list.
-
-=cut
-
-sub set_default_invoicing_list {
- my $self = shift;
- $self->invoicing_list($self->all_emails);
-}
-
-=item all_emails
-
-Returns the email addresses of all accounts provisioned for this customer.
-
-=cut
-
-sub all_emails {
- my $self = shift;
- my %list;
- foreach my $cust_pkg ( $self->all_pkgs ) {
- my @cust_svc = qsearch('cust_svc', { 'pkgnum' => $cust_pkg->pkgnum } );
- my @svc_acct =
- map { qsearchs('svc_acct', { 'svcnum' => $_->svcnum } ) }
- grep { qsearchs('svc_acct', { 'svcnum' => $_->svcnum } ) }
- @cust_svc;
- $list{$_}=1 foreach map { $_->email } @svc_acct;
- }
- keys %list;
-}
-
-=item invoicing_list_addpost
-
-Adds postal invoicing to this customer. If this customer is already configured
-to receive postal invoices, does nothing.
-
-=cut
-
-sub invoicing_list_addpost {
- my $self = shift;
- return if grep { $_ eq 'POST' } $self->invoicing_list;
- my @invoicing_list = $self->invoicing_list;
- push @invoicing_list, 'POST';
- $self->invoicing_list(\@invoicing_list);
-}
-
-=item invoicing_list_emailonly
-
-Returns the list of email invoice recipients (invoicing_list without non-email
-destinations such as POST and FAX).
-
-=cut
-
-sub invoicing_list_emailonly {
- my $self = shift;
- warn "$me invoicing_list_emailonly called"
- if $DEBUG;
- grep { $_ !~ /^([A-Z]+)$/ } $self->invoicing_list;
-}
-
-=item invoicing_list_emailonly_scalar
-
-Returns the list of email invoice recipients (invoicing_list without non-email
-destinations such as POST and FAX) as a comma-separated scalar.
-
-=cut
-
-sub invoicing_list_emailonly_scalar {
- my $self = shift;
- warn "$me invoicing_list_emailonly_scalar called"
- if $DEBUG;
- join(', ', $self->invoicing_list_emailonly);
-}
-
-=item referral_cust_main [ DEPTH [ EXCLUDE_HASHREF ] ]
-
-Returns an array of customers referred by this customer (referral_custnum set
-to this custnum). If DEPTH is given, recurses up to the given depth, returning
-customers referred by customers referred by this customer and so on, inclusive.
-The default behavior is DEPTH 1 (no recursion).
-
-=cut
-
-sub referral_cust_main {
- my $self = shift;
- my $depth = @_ ? shift : 1;
- my $exclude = @_ ? shift : {};
-
- my @cust_main =
- map { $exclude->{$_->custnum}++; $_; }
- grep { ! $exclude->{ $_->custnum } }
- qsearch( 'cust_main', { 'referral_custnum' => $self->custnum } );
-
- if ( $depth > 1 ) {
- push @cust_main,
- map { $_->referral_cust_main($depth-1, $exclude) }
- @cust_main;
- }
-
- @cust_main;
-}
-
-=item referral_cust_main_ncancelled
-
-Same as referral_cust_main, except only returns customers with uncancelled
-packages.
-
-=cut
-
-sub referral_cust_main_ncancelled {
- my $self = shift;
- grep { scalar($_->ncancelled_pkgs) } $self->referral_cust_main;
-}
-
-=item referral_cust_pkg [ DEPTH ]
-
-Like referral_cust_main, except returns a flat list of all unsuspended (and
-uncancelled) packages for each customer. The number of items in this list may
-be useful for comission calculations (perhaps after a C<grep { my $pkgpart = $_->pkgpart; grep { $_ == $pkgpart } @commission_worthy_pkgparts> } $cust_main-> ).
-
-=cut
-
-sub referral_cust_pkg {
- my $self = shift;
- my $depth = @_ ? shift : 1;
-
- map { $_->unsuspended_pkgs }
- grep { $_->unsuspended_pkgs }
- $self->referral_cust_main($depth);
-}
-
-=item referring_cust_main
-
-Returns the single cust_main record for the customer who referred this customer
-(referral_custnum), or false.
-
-=cut
-
-sub referring_cust_main {
- my $self = shift;
- return '' unless $self->referral_custnum;
- qsearchs('cust_main', { 'custnum' => $self->referral_custnum } );
-}
-
-=item credit AMOUNT, REASON
-
-Applies a credit to this customer. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub credit {
- my( $self, $amount, $reason, %options ) = @_;
- my $cust_credit = new FS::cust_credit {
- 'custnum' => $self->custnum,
- 'amount' => $amount,
- 'reason' => $reason,
- };
- $cust_credit->insert(%options);
-}
-
-=item charge AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ]
-
-Creates a one-time charge for this customer. If there is an error, returns
-the error, otherwise returns false.
-
-=cut
-
-sub charge {
- my $self = shift;
- my ( $amount, $pkg, $comment, $taxclass, $additional, $classnum );
- if ( ref( $_[0] ) ) {
- $amount = $_[0]->{amount};
- $pkg = exists($_[0]->{pkg}) ? $_[0]->{pkg} : 'One-time charge';
- $comment = exists($_[0]->{comment}) ? $_[0]->{comment}
- : '$'. sprintf("%.2f",$amount);
- $taxclass = exists($_[0]->{taxclass}) ? $_[0]->{taxclass} : '';
- $classnum = exists($_[0]->{classnum}) ? $_[0]->{classnum} : '';
- $additional = $_[0]->{additional};
- }else{
- $amount = shift;
- $pkg = @_ ? shift : 'One-time charge';
- $comment = @_ ? shift : '$'. sprintf("%.2f",$amount);
- $taxclass = @_ ? shift : '';
- $additional = [];
- }
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $part_pkg = new FS::part_pkg ( {
- 'pkg' => $pkg,
- 'comment' => $comment,
- 'plan' => 'flat',
- 'freq' => 0,
- 'disabled' => 'Y',
- 'classnum' => $classnum ? $classnum : '',
- 'taxclass' => $taxclass,
- } );
-
- my %options = ( ( map { ("additional_info$_" => $additional->[$_] ) }
- ( 0 .. @$additional - 1 )
- ),
- 'additional_count' => scalar(@$additional),
- 'setup_fee' => $amount,
- );
-
- my $error = $part_pkg->insert( options => \%options );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- my $pkgpart = $part_pkg->pkgpart;
- my %type_pkgs = ( 'typenum' => $self->agent->typenum, 'pkgpart' => $pkgpart );
- unless ( qsearchs('type_pkgs', \%type_pkgs ) ) {
- my $type_pkgs = new FS::type_pkgs \%type_pkgs;
- $error = $type_pkgs->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $cust_pkg = new FS::cust_pkg ( {
- 'custnum' => $self->custnum,
- 'pkgpart' => $pkgpart,
- } );
-
- $error = $cust_pkg->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item cust_bill
-
-Returns all the invoices (see L<FS::cust_bill>) for this customer.
-
-=cut
-
-sub cust_bill {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch('cust_bill', { 'custnum' => $self->custnum, } )
-}
-
-=item open_cust_bill
-
-Returns all the open (owed > 0) invoices (see L<FS::cust_bill>) for this
-customer.
-
-=cut
-
-sub open_cust_bill {
- my $self = shift;
- grep { $_->owed > 0 } $self->cust_bill;
-}
-
-=item cust_credit
-
-Returns all the credits (see L<FS::cust_credit>) for this customer.
-
-=cut
-
-sub cust_credit {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_credit', { 'custnum' => $self->custnum } )
-}
-
-=item cust_pay
-
-Returns all the payments (see L<FS::cust_pay>) for this customer.
-
-=cut
-
-sub cust_pay {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_pay', { 'custnum' => $self->custnum } )
-}
-
-=item cust_pay_void
-
-Returns all voided payments (see L<FS::cust_pay_void>) for this customer.
-
-=cut
-
-sub cust_pay_void {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_pay_void', { 'custnum' => $self->custnum } )
-}
-
-=item cust_pay_batch
-
-Returns all batched payments (see L<FS::cust_pay_void>) for this customer.
-
-=cut
-
-sub cust_pay_batch {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_pay_batch', { 'custnum' => $self->custnum } )
-}
-
-=item cust_refund
-
-Returns all the refunds (see L<FS::cust_refund>) for this customer.
-
-=cut
-
-sub cust_refund {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
-}
-
-=item name
-
-Returns a name string for this customer, either "Company (Last, First)" or
-"Last, First".
-
-=cut
-
-sub name {
- my $self = shift;
- my $name = $self->contact;
- $name = $self->company. " ($name)" if $self->company;
- $name;
-}
-
-=item ship_name
-
-Returns a name string for this (service/shipping) contact, either
-"Company (Last, First)" or "Last, First".
-
-=cut
-
-sub ship_name {
- my $self = shift;
- if ( $self->get('ship_last') ) {
- my $name = $self->ship_contact;
- $name = $self->ship_company. " ($name)" if $self->ship_company;
- $name;
- } else {
- $self->name;
- }
-}
-
-=item contact
-
-Returns this customer's full (billing) contact name only, "Last, First"
-
-=cut
-
-sub contact {
- my $self = shift;
- $self->get('last'). ', '. $self->first;
-}
-
-=item ship_contact
-
-Returns this customer's full (shipping) contact name only, "Last, First"
-
-=cut
-
-sub ship_contact {
- my $self = shift;
- $self->get('ship_last')
- ? $self->get('ship_last'). ', '. $self->ship_first
- : $self->contact;
-}
-
-=item country_full
-
-Returns this customer's full country name
-
-=cut
-
-sub country_full {
- my $self = shift;
- code2country($self->country);
-}
-
-=item cust_status
-
-=item status
-
-Returns a status string for this customer, currently:
-
-=over 4
-
-=item prospect - No packages have ever been ordered
-
-=item active - One or more recurring packages is active
-
-=item inactive - No active recurring packages, but otherwise unsuspended/uncancelled (the inactive status is new - previously inactive customers were mis-identified as cancelled)
-
-=item suspended - All non-cancelled recurring packages are suspended
-
-=item cancelled - All recurring packages are cancelled
-
-=back
-
-=cut
-
-sub status { shift->cust_status(@_); }
-
-sub cust_status {
- my $self = shift;
- for my $status (qw( prospect active inactive suspended cancelled )) {
- my $method = $status.'_sql';
- my $numnum = ( my $sql = $self->$method() ) =~ s/cust_main\.custnum/?/g;
- my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr;
- $sth->execute( ($self->custnum) x $numnum )
- or die "Error executing 'SELECT $sql': ". $sth->errstr;
- return $status if $sth->fetchrow_arrayref->[0];
- }
-}
-
-=item ucfirst_cust_status
-
-=item ucfirst_status
-
-Returns the status with the first character capitalized.
-
-=cut
-
-sub ucfirst_status { shift->ucfirst_cust_status(@_); }
-
-sub ucfirst_cust_status {
- my $self = shift;
- ucfirst($self->cust_status);
-}
-
-=item statuscolor
-
-Returns a hex triplet color string for this customer's status.
-
-=cut
-
-use vars qw(%statuscolor);
-tie my %statuscolor, 'Tie::IxHash',
- 'prospect' => '7e0079', #'000000', #black? naw, purple
- 'active' => '00CC00', #green
- 'inactive' => '0000CC', #blue
- 'suspended' => 'FF9900', #yellow
- 'cancelled' => 'FF0000', #red
-;
-
-sub statuscolor { shift->cust_statuscolor(@_); }
-
-sub cust_statuscolor {
- my $self = shift;
- $statuscolor{$self->cust_status};
-}
-
-=item tickets
-
-Returns an array of hashes representing the customer's RT tickets.
-
-=cut
-
-sub tickets {
- my $self = shift;
-
- my $num = $conf->config('cust_main-max_tickets') || 10;
- my @tickets = ();
-
- unless ( $conf->config('ticket_system-custom_priority_field') ) {
-
- @tickets = @{ FS::TicketSystem->customer_tickets($self->custnum, $num) };
-
- } else {
-
- foreach my $priority (
- $conf->config('ticket_system-custom_priority_field-values'), ''
- ) {
- last if scalar(@tickets) >= $num;
- push @tickets,
- @{ FS::TicketSystem->customer_tickets( $self->custnum,
- $num - scalar(@tickets),
- $priority,
- )
- };
- }
- }
- (@tickets);
-}
-
-# Return services representing svc_accts in customer support packages
-sub support_services {
- my $self = shift;
- my %packages = map { $_ => 1 } $conf->config('support_packages');
-
- grep { $_->pkg_svc && $_->pkg_svc->primary_svc eq 'Y' }
- grep { $_->part_svc->svcdb eq 'svc_acct' }
- map { $_->cust_svc }
- grep { exists $packages{ $_->pkgpart } }
- $self->ncancelled_pkgs;
-
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item statuses
-
-Class method that returns the list of possible status strings for customers
-(see L<the status method|/status>). For example:
-
- @statuses = FS::cust_main->statuses();
-
-=cut
-
-sub statuses {
- #my $self = shift; #could be class...
- keys %statuscolor;
-}
-
-=item prospect_sql
-
-Returns an SQL expression identifying prospective cust_main records (customers
-with no packages ever ordered)
-
-=cut
-
-use vars qw($select_count_pkgs);
-$select_count_pkgs =
- "SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum";
-
-sub select_count_pkgs_sql {
- $select_count_pkgs;
-}
-
-sub prospect_sql { "
- 0 = ( $select_count_pkgs )
-"; }
-
-=item active_sql
-
-Returns an SQL expression identifying active cust_main records (customers with
-no active recurring packages, but otherwise unsuspended/uncancelled).
-
-=cut
-
-sub active_sql { "
- 0 < ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. "
- )
-"; }
-
-=item inactive_sql
-
-Returns an SQL expression identifying inactive cust_main records (customers with
-active recurring packages).
-
-=cut
-
-sub inactive_sql { "
- 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " )
- AND
- 0 < ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " )
-"; }
-
-=item susp_sql
-=item suspended_sql
-
-Returns an SQL expression identifying suspended cust_main records.
-
-=cut
-
-
-sub suspended_sql { susp_sql(@_); }
-sub susp_sql { "
- 0 < ( $select_count_pkgs AND ". FS::cust_pkg->suspended_sql. " )
- AND
- 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " )
-"; }
-
-=item cancel_sql
-=item cancelled_sql
-
-Returns an SQL expression identifying cancelled cust_main records.
-
-=cut
-
-sub cancelled_sql { cancel_sql(@_); }
-sub cancel_sql {
-
- my $recurring_sql = FS::cust_pkg->recurring_sql;
- #my $recurring_sql = "
- # '0' != ( select freq from part_pkg
- # where cust_pkg.pkgpart = part_pkg.pkgpart )
- #";
-
- "
- 0 < ( $select_count_pkgs )
- AND 0 = ( $select_count_pkgs AND $recurring_sql
- AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
- )
- ";
-}
-
-=item uncancel_sql
-=item uncancelled_sql
-
-Returns an SQL expression identifying un-cancelled cust_main records.
-
-=cut
-
-sub uncancelled_sql { uncancel_sql(@_); }
-sub uncancel_sql { "
- ( 0 < ( $select_count_pkgs
- AND ( cust_pkg.cancel IS NULL
- OR cust_pkg.cancel = 0
- )
- )
- OR 0 = ( $select_count_pkgs )
- )
-"; }
-
-=item balance_sql
-
-Returns an SQL fragment to retreive the balance.
-
-=cut
-
-sub balance_sql { "
- ( SELECT COALESCE( SUM(charged), 0 ) FROM cust_bill
- WHERE cust_bill.custnum = cust_main.custnum )
- - ( SELECT COALESCE( SUM(paid), 0 ) FROM cust_pay
- WHERE cust_pay.custnum = cust_main.custnum )
- - ( SELECT COALESCE( SUM(amount), 0 ) FROM cust_credit
- WHERE cust_credit.custnum = cust_main.custnum )
- + ( SELECT COALESCE( SUM(refund), 0 ) FROM cust_refund
- WHERE cust_refund.custnum = cust_main.custnum )
-"; }
-
-=item balance_date_sql START_TIME [ END_TIME [ OPTION => VALUE ... ] ]
-
-Returns an SQL fragment to retreive the balance for this customer, only
-considering invoices with date earlier than START_TIME, and optionally not
-later than END_TIME (total_owed_date minus total_credited minus
-total_unapplied_payments).
-
-Times are specified as SQL fragments or numeric
-UNIX timestamps; see L<perlfunc/"time">). Also see L<Time::Local> and
-L<Date::Parse> for conversion functions. The empty string can be passed
-to disable that time constraint completely.
-
-Available options are:
-
-=over 4
-
-=item unapplied_date - set to true to disregard unapplied credits, payments and refunds outside the specified time period - by default the time period restriction only applies to invoices (useful for reporting, probably a bad idea for event triggering)
-
-=item total - set to true to remove all customer comparison clauses, for totals
-
-=item where - WHERE clause hashref (elements "AND"ed together) (typically used with the total option)
-
-=item join - JOIN clause (typically used with the total option)
-
-=item
-
-=back
-
-=cut
-
-sub balance_date_sql {
- my( $class, $start, $end, %opt ) = @_;
-
- my $owed = FS::cust_bill->owed_sql;
- my $unapp_refund = FS::cust_refund->unapplied_sql;
- my $unapp_credit = FS::cust_credit->unapplied_sql;
- my $unapp_pay = FS::cust_pay->unapplied_sql;
-
- my $j = $opt{'join'} || '';
-
- my $owed_wh = $class->_money_table_where( 'cust_bill', $start,$end,%opt );
- my $refund_wh = $class->_money_table_where( 'cust_refund', $start,$end,%opt );
- my $credit_wh = $class->_money_table_where( 'cust_credit', $start,$end,%opt );
- my $pay_wh = $class->_money_table_where( 'cust_pay', $start,$end,%opt );
-
- " ( SELECT COALESCE(SUM($owed), 0) FROM cust_bill $j $owed_wh )
- + ( SELECT COALESCE(SUM($unapp_refund), 0) FROM cust_refund $j $refund_wh )
- - ( SELECT COALESCE(SUM($unapp_credit), 0) FROM cust_credit $j $credit_wh )
- - ( SELECT COALESCE(SUM($unapp_pay), 0) FROM cust_pay $j $pay_wh )
- ";
-
-}
-
-=item _money_table_where TABLE START_TIME [ END_TIME [ OPTION => VALUE ... ] ]
-
-Helper method for balance_date_sql; name (and usage) subject to change
-(suggestions welcome).
-
-Returns a WHERE clause for the specified monetary TABLE (cust_bill,
-cust_refund, cust_credit or cust_pay).
-
-If TABLE is "cust_bill" or the unapplied_date option is true, only
-considers records with date earlier than START_TIME, and optionally not
-later than END_TIME .
-
-=cut
-
-sub _money_table_where {
- my( $class, $table, $start, $end, %opt ) = @_;
-
- my @where = ();
- push @where, "cust_main.custnum = $table.custnum" unless $opt{'total'};
- if ( $table eq 'cust_bill' || $opt{'unapplied_date'} ) {
- push @where, "$table._date <= $start" if defined($start) && length($start);
- push @where, "$table._date > $end" if defined($end) && length($end);
- }
- push @where, @{$opt{'where'}} if $opt{'where'};
- my $where = scalar(@where) ? 'WHERE '. join(' AND ', @where ) : '';
-
- $where;
-
-}
-
-=item fuzzy_search FUZZY_HASHREF [ HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ ]
-
-Performs a fuzzy (approximate) search and returns the matching FS::cust_main
-records. Currently, I<first>, I<last> and/or I<company> may be specified (the
-appropriate ship_ field is also searched).
-
-Additional options are the same as FS::Record::qsearch
-
-=cut
-
-sub fuzzy_search {
- my( $self, $fuzzy, $hash, @opt) = @_;
- #$self
- $hash ||= {};
- my @cust_main = ();
-
- check_and_rebuild_fuzzyfiles();
- foreach my $field ( keys %$fuzzy ) {
-
- my $all = $self->all_X($field);
- next unless scalar(@$all);
-
- my %match = ();
- $match{$_}=1 foreach ( amatch( $fuzzy->{$field}, ['i'], @$all ) );
-
- my @fcust = ();
- foreach ( keys %match ) {
- push @fcust, qsearch('cust_main', { %$hash, $field=>$_}, @opt);
- push @fcust, qsearch('cust_main', { %$hash, "ship_$field"=>$_}, @opt);
- }
- my %fsaw = ();
- push @cust_main, grep { ! $fsaw{$_->custnum}++ } @fcust;
- }
-
- # we want the components of $fuzzy ANDed, not ORed, but still don't want dupes
- my %saw = ();
- @cust_main = grep { ++$saw{$_->custnum} == scalar(keys %$fuzzy) } @cust_main;
-
- @cust_main;
-
-}
-
-=item masked FIELD
-
-Returns a masked version of the named field
-
-=cut
-
-sub masked {
-my ($self,$field) = @_;
-
-# Show last four
-
-'x'x(length($self->getfield($field))-4).
- substr($self->getfield($field), (length($self->getfield($field))-4));
-
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item smart_search OPTION => VALUE ...
-
-Accepts the following options: I<search>, the string to search for. The string
-will be searched for as a customer number, phone number, name or company name,
-as an exact, or, in some cases, a substring or fuzzy match (see the source code
-for the exact heuristics used); I<no_fuzzy_on_exact>, causes smart_search to
-skip fuzzy matching when an exact match is found.
-
-Any additional options are treated as an additional qualifier on the search
-(i.e. I<agentnum>).
-
-Returns a (possibly empty) array of FS::cust_main objects.
-
-=cut
-
-sub smart_search {
- my %options = @_;
-
- #here is the agent virtualization
- my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql;
-
- my @cust_main = ();
-
- my $skip_fuzzy = delete $options{'no_fuzzy_on_exact'};
- my $search = delete $options{'search'};
- ( my $alphanum_search = $search ) =~ s/\W//g;
-
- if ( $alphanum_search =~ /^1?(\d{3})(\d{3})(\d{4})(\d*)$/ ) { #phone# search
-
- #false laziness w/Record::ut_phone
- my $phonen = "$1-$2-$3";
- $phonen .= " x$4" if $4;
-
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => { %options },
- 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ).
- ' ( '.
- join(' OR ', map "$_ = '$phonen'",
- qw( daytime night fax
- ship_daytime ship_night ship_fax )
- ).
- ' ) '.
- " AND $agentnums_sql", #agent virtualization
- } );
-
- unless ( @cust_main || $phonen =~ /x\d+$/ ) { #no exact match
- #try looking for matches with extensions unless one was specified
-
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => { %options },
- 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ).
- ' ( '.
- join(' OR ', map "$_ LIKE '$phonen\%'",
- qw( daytime night
- ship_daytime ship_night )
- ).
- ' ) '.
- " AND $agentnums_sql", #agent virtualization
- } );
-
- }
-
- # custnum search (also try agent_custid), with some tweaking options if your
- # legacy cust "numbers" have letters
- } elsif ( $search =~ /^\s*(\d+)\s*$/
- || ( $conf->config('cust_main-agent_custid-format') eq 'ww?d+'
- && $search =~ /^\s*(\w\w?\d+)\s*$/
- )
- )
- {
-
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => { 'custnum' => $1, %options },
- 'extra_sql' => " AND $agentnums_sql", #agent virtualization
- } );
-
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => { 'agent_custid' => $1, %options },
- 'extra_sql' => " AND $agentnums_sql", #agent virtualization
- } );
-
- } elsif ( $search =~ /^\s*(\S.*\S)\s+\((.+), ([^,]+)\)\s*$/ ) {
-
- my($company, $last, $first) = ( $1, $2, $3 );
-
- # "Company (Last, First)"
- #this is probably something a browser remembered,
- #so just do an exact search
-
- foreach my $prefix ( '', 'ship_' ) {
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => { $prefix.'first' => $first,
- $prefix.'last' => $last,
- $prefix.'company' => $company,
- %options,
- },
- 'extra_sql' => " AND $agentnums_sql",
- } );
- }
-
- } elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { # value search
- # try (ship_){last,company}
-
- my $value = lc($1);
-
- # # remove "(Last, First)" in "Company (Last, First)", otherwise the
- # # full strings the browser remembers won't work
- # $value =~ s/\([\w \,\.\-\']*\)$//; #false laziness w/Record::ut_name
-
- use Lingua::EN::NameParse;
- my $NameParse = new Lingua::EN::NameParse(
- auto_clean => 1,
- allow_reversed => 1,
- );
-
- my($last, $first) = ( '', '' );
- #maybe disable this too and just rely on NameParse?
- if ( $value =~ /^(.+),\s*([^,]+)$/ ) { # Last, First
-
- ($last, $first) = ( $1, $2 );
-
- #} elsif ( $value =~ /^(.+)\s+(.+)$/ ) {
- } elsif ( ! $NameParse->parse($value) ) {
-
- my %name = $NameParse->components;
- $first = $name{'given_name_1'};
- $last = $name{'surname_1'};
-
- }
-
- if ( $first && $last ) {
-
- my($q_last, $q_first) = ( dbh->quote($last), dbh->quote($first) );
-
- #exact
- my $sql = scalar(keys %options) ? ' AND ' : ' WHERE ';
- $sql .= "
- ( ( LOWER(last) = $q_last AND LOWER(first) = $q_first )
- OR ( LOWER(ship_last) = $q_last AND LOWER(ship_first) = $q_first )
- )";
-
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => \%options,
- 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization
- } );
-
- # or it just be something that was typed in... (try that in a sec)
-
- }
-
- my $q_value = dbh->quote($value);
-
- #exact
- my $sql = scalar(keys %options) ? ' AND ' : ' WHERE ';
- $sql .= " ( LOWER(last) = $q_value
- OR LOWER(company) = $q_value
- OR LOWER(ship_last) = $q_value
- OR LOWER(ship_company) = $q_value
- )";
-
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => \%options,
- 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization
- } );
-
- #no exact match, trying substring/fuzzy
- #always do substring & fuzzy (unless they're explicity config'ed off)
- #getting complaints searches are not returning enough
- unless ( @cust_main && $skip_fuzzy || $conf->exists('disable-fuzzy') ) {
-
- #still some false laziness w/ search/cust_main.cgi
-
- #substring
-
- my @hashrefs = (
- { 'company' => { op=>'ILIKE', value=>"%$value%" }, },
- { 'ship_company' => { op=>'ILIKE', value=>"%$value%" }, },
- );
-
- if ( $first && $last ) {
-
- push @hashrefs,
- { 'first' => { op=>'ILIKE', value=>"%$first%" },
- 'last' => { op=>'ILIKE', value=>"%$last%" },
- },
- { 'ship_first' => { op=>'ILIKE', value=>"%$first%" },
- 'ship_last' => { op=>'ILIKE', value=>"%$last%" },
- },
- ;
-
- } else {
-
- push @hashrefs,
- { 'last' => { op=>'ILIKE', value=>"%$value%" }, },
- { 'ship_last' => { op=>'ILIKE', value=>"%$value%" }, },
- ;
- }
-
- foreach my $hashref ( @hashrefs ) {
-
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => { %$hashref,
- %options,
- },
- 'extra_sql' => " AND $agentnums_sql", #agent virtualizaiton
- } );
-
- }
-
- #fuzzy
- my @fuzopts = (
- \%options, #hashref
- '', #select
- " AND $agentnums_sql", #extra_sql #agent virtualization
- );
-
- if ( $first && $last ) {
- push @cust_main, FS::cust_main->fuzzy_search(
- { 'last' => $last, #fuzzy hashref
- 'first' => $first }, #
- @fuzopts
- );
- }
- foreach my $field ( 'last', 'company' ) {
- push @cust_main,
- FS::cust_main->fuzzy_search( { $field => $value }, @fuzopts );
- }
-
- }
-
- #eliminate duplicates
- my %saw = ();
- @cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
-
- }
-
- @cust_main;
-
-}
-
-=item check_and_rebuild_fuzzyfiles
-
-=cut
-
-use vars qw(@fuzzyfields);
-@fuzzyfields = ( 'last', 'first', 'company' );
-
-sub check_and_rebuild_fuzzyfiles {
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- rebuild_fuzzyfiles() if grep { ! -e "$dir/cust_main.$_" } @fuzzyfields
-}
-
-=item rebuild_fuzzyfiles
-
-=cut
-
-sub rebuild_fuzzyfiles {
-
- use Fcntl qw(:flock);
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- mkdir $dir, 0700 unless -d $dir;
-
- foreach my $fuzzy ( @fuzzyfields ) {
-
- open(LOCK,">>$dir/cust_main.$fuzzy")
- or die "can't open $dir/cust_main.$fuzzy: $!";
- flock(LOCK,LOCK_EX)
- or die "can't lock $dir/cust_main.$fuzzy: $!";
-
- open (CACHE,">$dir/cust_main.$fuzzy.tmp")
- or die "can't open $dir/cust_main.$fuzzy.tmp: $!";
-
- foreach my $field ( $fuzzy, "ship_$fuzzy" ) {
- my $sth = dbh->prepare("SELECT $field FROM cust_main".
- " WHERE $field != '' AND $field IS NOT NULL");
- $sth->execute or die $sth->errstr;
-
- while ( my $row = $sth->fetchrow_arrayref ) {
- print CACHE $row->[0]. "\n";
- }
-
- }
-
- close CACHE or die "can't close $dir/cust_main.$fuzzy.tmp: $!";
-
- rename "$dir/cust_main.$fuzzy.tmp", "$dir/cust_main.$fuzzy";
- close LOCK;
- }
-
-}
-
-=item all_X
-
-=cut
-
-sub all_X {
- my( $self, $field ) = @_;
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- open(CACHE,"<$dir/cust_main.$field")
- or die "can't open $dir/cust_main.$field: $!";
- my @array = map { chomp; $_; } <CACHE>;
- close CACHE;
- \@array;
-}
-
-=item append_fuzzyfiles LASTNAME COMPANY
-
-=cut
-
-sub append_fuzzyfiles {
- #my( $first, $last, $company ) = @_;
-
- &check_and_rebuild_fuzzyfiles;
-
- use Fcntl qw(:flock);
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
-
- foreach my $field (qw( first last company )) {
- my $value = shift;
-
- if ( $value ) {
-
- open(CACHE,">>$dir/cust_main.$field")
- or die "can't open $dir/cust_main.$field: $!";
- flock(CACHE,LOCK_EX)
- or die "can't lock $dir/cust_main.$field: $!";
-
- print CACHE "$value\n";
-
- flock(CACHE,LOCK_UN)
- or die "can't unlock $dir/cust_main.$field: $!";
- close CACHE;
- }
-
- }
-
- 1;
-}
-
-=item batch_import
-
-=cut
-
-sub batch_import {
- my $param = shift;
- #warn join('-',keys %$param);
- my $fh = $param->{filehandle};
- my $agentnum = $param->{agentnum};
-
- my $refnum = $param->{refnum};
- my $pkgpart = $param->{pkgpart};
-
- #my @fields = @{$param->{fields}};
- my $format = $param->{'format'};
- my @fields;
- my $payby;
- if ( $format eq 'simple' ) {
- @fields = qw( cust_pkg.setup dayphone first last
- address1 address2 city state zip comments );
- $payby = 'BILL';
- } elsif ( $format eq 'extended' ) {
- @fields = qw( agent_custid refnum
- last first address1 address2 city state zip country
- daytime night
- ship_last ship_first ship_address1 ship_address2
- ship_city ship_state ship_zip ship_country
- payinfo paycvv paydate
- invoicing_list
- cust_pkg.pkgpart
- svc_acct.username svc_acct._password
- );
- $payby = 'BILL';
- } elsif ( $format eq 'extended-plus_company' ) {
- @fields = qw( agent_custid refnum
- last first company address1 address2 city state zip country
- daytime night
- ship_last ship_first ship_company ship_address1 ship_address2
- ship_city ship_state ship_zip ship_country
- payinfo paycvv paydate
- invoicing_list
- cust_pkg.pkgpart
- svc_acct.username svc_acct._password
- );
- $payby = 'BILL';
- } else {
- die "unknown format $format";
- }
-
- eval "use Text::CSV_XS;";
- die $@ if $@;
-
- my $csv = new Text::CSV_XS;
- #warn $csv;
- #warn $fh;
-
- my $imported = 0;
- #my $columns;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- #while ( $columns = $csv->getline($fh) ) {
- my $line;
- while ( defined($line=<$fh>) ) {
-
- $csv->parse($line) or do {
- $dbh->rollback if $oldAutoCommit;
- return "can't parse: ". $csv->error_input();
- };
-
- my @columns = $csv->fields();
- #warn join('-',@columns);
-
- my %cust_main = (
- agentnum => $agentnum,
- refnum => $refnum,
- country => $conf->config('countrydefault') || 'US',
- payby => $payby, #default
- paydate => '12/2037', #default
- );
- my $billtime = time;
- my %cust_pkg = ( pkgpart => $pkgpart );
- my %svc_acct = ();
- foreach my $field ( @fields ) {
-
- if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) {
-
- #$cust_pkg{$1} = str2time( shift @$columns );
- if ( $1 eq 'pkgpart' ) {
- $cust_pkg{$1} = shift @columns;
- } elsif ( $1 eq 'setup' ) {
- $billtime = str2time(shift @columns);
- } else {
- $cust_pkg{$1} = str2time( shift @columns );
- }
-
- } elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) {
-
- $svc_acct{$1} = shift @columns;
-
- } else {
-
- #refnum interception
- if ( $field eq 'refnum' && $columns[0] !~ /^\s*(\d+)\s*$/ ) {
-
- my $referral = $columns[0];
- my %hash = ( 'referral' => $referral,
- 'agentnum' => $agentnum,
- 'disabled' => '',
- );
-
- my $part_referral = qsearchs('part_referral', \%hash )
- || new FS::part_referral \%hash;
-
- unless ( $part_referral->refnum ) {
- my $error = $part_referral->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't auto-insert advertising source: $referral: $error";
- }
- }
-
- $columns[0] = $part_referral->refnum;
- }
-
- #$cust_main{$field} = shift @$columns;
- $cust_main{$field} = shift @columns;
- }
- }
-
- $cust_main{'payby'} = 'CARD' if length($cust_main{'payinfo'});
-
- my $invoicing_list = $cust_main{'invoicing_list'}
- ? [ delete $cust_main{'invoicing_list'} ]
- : [];
-
- my $cust_main = new FS::cust_main ( \%cust_main );
-
- use Tie::RefHash;
- tie my %hash, 'Tie::RefHash'; #this part is important
-
- if ( $cust_pkg{'pkgpart'} ) {
- my $cust_pkg = new FS::cust_pkg ( \%cust_pkg );
-
- my @svc_acct = ();
- if ( $svc_acct{'username'} ) {
- my $part_pkg = $cust_pkg->part_pkg;
- unless ( $part_pkg ) {
- $dbh->rollback if $oldAutoCommit;
- return "unknown pkgpart: ". $cust_pkg{'pkgpart'};
- }
- $svc_acct{svcpart} = $part_pkg->svcpart( 'svc_acct' );
- push @svc_acct, new FS::svc_acct ( \%svc_acct )
- }
-
- $hash{$cust_pkg} = \@svc_acct;
- }
-
- my $error = $cust_main->insert( \%hash, $invoicing_list );
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't insert customer for $line: $error";
- }
-
- if ( $format eq 'simple' ) {
-
- #false laziness w/bill.cgi
- $error = $cust_main->bill( 'time' => $billtime );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't bill customer for $line: $error";
- }
-
- $error = $cust_main->apply_payments_and_credits;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't bill customer for $line: $error";
- }
-
- $error = $cust_main->collect();
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't collect customer for $line: $error";
- }
-
- }
-
- $imported++;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- return "Empty file!" unless $imported;
-
- ''; #no error
-
-}
-
-=item batch_charge
-
-=cut
-
-sub batch_charge {
- my $param = shift;
- #warn join('-',keys %$param);
- my $fh = $param->{filehandle};
- my @fields = @{$param->{fields}};
-
- eval "use Text::CSV_XS;";
- die $@ if $@;
-
- my $csv = new Text::CSV_XS;
- #warn $csv;
- #warn $fh;
-
- my $imported = 0;
- #my $columns;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- #while ( $columns = $csv->getline($fh) ) {
- my $line;
- while ( defined($line=<$fh>) ) {
-
- $csv->parse($line) or do {
- $dbh->rollback if $oldAutoCommit;
- return "can't parse: ". $csv->error_input();
- };
-
- my @columns = $csv->fields();
- #warn join('-',@columns);
-
- my %row = ();
- foreach my $field ( @fields ) {
- $row{$field} = shift @columns;
- }
-
- my $cust_main = qsearchs('cust_main', { 'custnum' => $row{'custnum'} } );
- unless ( $cust_main ) {
- $dbh->rollback if $oldAutoCommit;
- return "unknown custnum $row{'custnum'}";
- }
-
- if ( $row{'amount'} > 0 ) {
- my $error = $cust_main->charge($row{'amount'}, $row{'pkg'});
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $imported++;
- } elsif ( $row{'amount'} < 0 ) {
- my $error = $cust_main->credit( sprintf( "%.2f", 0-$row{'amount'} ),
- $row{'pkg'} );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $imported++;
- } else {
- #hmm?
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- return "Empty file!" unless $imported;
-
- ''; #no error
-
-}
-
-=item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
-
-Sends a templated email notification to the customer (see L<Text::Template>).
-
-OPTIONS is a hash and may include
-
-I<from> - the email sender (default is invoice_from)
-
-I<to> - comma-separated scalar or arrayref of recipients
- (default is invoicing_list)
-
-I<subject> - The subject line of the sent email notification
- (default is "Notice from company_name")
-
-I<extra_fields> - a hashref of name/value pairs which will be substituted
- into the template
-
-The following variables are vavailable in the template.
-
-I<$first> - the customer first name
-I<$last> - the customer last name
-I<$company> - the customer company
-I<$payby> - a description of the method of payment for the customer
- # would be nice to use FS::payby::shortname
-I<$payinfo> - the account information used to collect for this customer
-I<$expdate> - the expiration of the customer payment in seconds from epoch
-
-=cut
-
-sub notify {
- my ($customer, $template, %options) = @_;
-
- return unless $conf->exists($template);
-
- my $from = $conf->config('invoice_from') if $conf->exists('invoice_from');
- $from = $options{from} if exists($options{from});
-
- my $to = join(',', $customer->invoicing_list_emailonly);
- $to = $options{to} if exists($options{to});
-
- my $subject = "Notice from " . $conf->config('company_name')
- if $conf->exists('company_name');
- $subject = $options{subject} if exists($options{subject});
-
- my $notify_template = new Text::Template (TYPE => 'ARRAY',
- SOURCE => [ map "$_\n",
- $conf->config($template)]
- )
- or die "can't create new Text::Template object: Text::Template::ERROR";
- $notify_template->compile()
- or die "can't compile template: Text::Template::ERROR";
-
- $FS::notify_template::_template::company_name = $conf->config('company_name');
- $FS::notify_template::_template::company_address =
- join("\n", $conf->config('company_address') ). "\n";
-
- my $paydate = $customer->paydate || '2037-12-31';
- $FS::notify_template::_template::first = $customer->first;
- $FS::notify_template::_template::last = $customer->last;
- $FS::notify_template::_template::company = $customer->company;
- $FS::notify_template::_template::payinfo = $customer->mask_payinfo;
- my $payby = $customer->payby;
- my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
- my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
-
- #credit cards expire at the end of the month/year of their exp date
- if ($payby eq 'CARD' || $payby eq 'DCRD') {
- $FS::notify_template::_template::payby = 'credit card';
- ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
- $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
- $expire_time--;
- }elsif ($payby eq 'COMP') {
- $FS::notify_template::_template::payby = 'complimentary account';
- }else{
- $FS::notify_template::_template::payby = 'current method';
- }
- $FS::notify_template::_template::expdate = $expire_time;
-
- for (keys %{$options{extra_fields}}){
- no strict "refs";
- ${"FS::notify_template::_template::$_"} = $options{extra_fields}->{$_};
- }
-
- send_email(from => $from,
- to => $to,
- subject => $subject,
- body => $notify_template->fill_in( PACKAGE =>
- 'FS::notify_template::_template' ),
- );
-
-}
-
-=item generate_letter CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
-
-Generates a templated notification to the customer (see L<Text::Template>).
-
-OPTIONS is a hash and may include
-
-I<extra_fields> - a hashref of name/value pairs which will be substituted
- into the template. These values may override values mentioned below
- and those from the customer record.
-
-The following variables are available in the template instead of or in addition
-to the fields of the customer record.
-
-I<$payby> - a description of the method of payment for the customer
- # would be nice to use FS::payby::shortname
-I<$payinfo> - the masked account information used to collect for this customer
-I<$expdate> - the expiration of the customer payment method in seconds from epoch
-I<$returnaddress> - the return address defaults to invoice_latexreturnaddress or company_address
-
-=cut
-
-sub generate_letter {
- my ($self, $template, %options) = @_;
-
- return unless $conf->exists($template);
-
- my $letter_template = new Text::Template
- ( TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", $conf->config($template)],
- DELIMITERS => [ '[@--', '--@]' ],
- )
- or die "can't create new Text::Template object: Text::Template::ERROR";
-
- $letter_template->compile()
- or die "can't compile template: Text::Template::ERROR";
-
- my %letter_data = map { $_ => $self->$_ } $self->fields;
- $letter_data{payinfo} = $self->mask_payinfo;
-
- #my $paydate = $self->paydate || '2037-12-31';
- my $paydate = $self->paydate =~ /^\S+$/ ? $self->paydate : '2037-12-31';
-
- my $payby = $self->payby;
- my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
- my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
-
- #credit cards expire at the end of the month/year of their exp date
- if ($payby eq 'CARD' || $payby eq 'DCRD') {
- $letter_data{payby} = 'credit card';
- ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
- $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
- $expire_time--;
- }elsif ($payby eq 'COMP') {
- $letter_data{payby} = 'complimentary account';
- }else{
- $letter_data{payby} = 'current method';
- }
- $letter_data{expdate} = $expire_time;
-
- for (keys %{$options{extra_fields}}){
- $letter_data{$_} = $options{extra_fields}->{$_};
- }
-
- unless(exists($letter_data{returnaddress})){
- my $retadd = join("\n", $conf->config_orbase( 'invoice_latexreturnaddress',
- $self->agent_template)
- );
- if ( length($retadd) ) {
- $letter_data{returnaddress} = $retadd;
- } elsif ( grep /\S/, $conf->config('company_address') ) {
- $letter_data{returnaddress} =
- join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg,
- $conf->config('company_address')
- );
- } else {
- $letter_data{returnaddress} = '~';
- }
- }
-
- $letter_data{conf_dir} = "$FS::UID::conf_dir/conf.$FS::UID::datasrc";
-
- $letter_data{company_name} = $conf->config('company_name');
-
- my $dir = $FS::UID::conf_dir."cache.". $FS::UID::datasrc;
- my $fh = new File::Temp( TEMPLATE => 'letter.'. $self->custnum. '.XXXXXXXX',
- DIR => $dir,
- SUFFIX => '.tex',
- UNLINK => 0,
- ) or die "can't open temp file: $!\n";
-
- $letter_template->fill_in( OUTPUT => $fh, HASH => \%letter_data );
- close $fh;
- $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
- return $1;
-}
-
-=item print_ps TEMPLATE
-
-Returns an postscript letter filled in from TEMPLATE, as a scalar.
-
-=cut
-
-sub print_ps {
- my $self = shift;
- my $file = $self->generate_letter(@_);
- FS::Misc::generate_ps($file);
-}
-
-=item print TEMPLATE
-
-Prints the filled in template.
-
-TEMPLATE is the name of a L<Text::Template> to fill in and print.
-
-=cut
-
-sub queueable_print {
- my %opt = @_;
-
- my $self = qsearchs('cust_main', { 'custnum' => $opt{custnum} } )
- or die "invalid customer number: " . $opt{custvnum};
-
- my $error = $self->print( $opt{template} );
- die $error if $error;
-}
-
-sub print {
- my ($self, $template) = (shift, shift);
- do_print [ $self->print_ps($template) ];
-}
-
-sub agent_template {
- my $self = shift;
- $self->_agent_plandata('agent_templatename');
-}
-
-sub agent_invoice_from {
- my $self = shift;
- $self->_agent_plandata('agent_invoice_from');
-}
-
-sub _agent_plandata {
- my( $self, $option ) = @_;
-
- #yuck. this whole thing needs to be reconciled better with 1.9's idea of
- #agent-specific Conf
-
- use FS::part_event::Condition;
-
- my $agentnum = $self->agentnum;
-
- my $regexp = '';
- if ( driver_name =~ /^Pg/i ) {
- $regexp = '~';
- } elsif ( driver_name =~ /^mysql/i ) {
- $regexp = 'REGEXP';
- } else {
- die "don't know how to use regular expressions in ". driver_name. " databases";
- }
-
- my $part_event_option =
- qsearchs({
- 'select' => 'part_event_option.*',
- 'table' => 'part_event_option',
- 'addl_from' => q{
- LEFT JOIN part_event USING ( eventpart )
- LEFT JOIN part_event_option AS peo_agentnum
- ON ( part_event.eventpart = peo_agentnum.eventpart
- AND peo_agentnum.optionname = 'agentnum'
- AND peo_agentnum.optionvalue }. $regexp. q{ '(^|,)}. $agentnum. q{(,|$)'
- )
- LEFT JOIN part_event_option AS peo_cust_bill_age
- ON ( part_event.eventpart = peo_cust_bill_age.eventpart
- AND peo_cust_bill_age.optionname = 'cust_bill_age'
- )
- },
- #'hashref' => { 'optionname' => $option },
- #'hashref' => { 'part_event_option.optionname' => $option },
- 'extra_sql' =>
- " WHERE part_event_option.optionname = ". dbh->quote($option).
- " AND action = 'cust_bill_send_agent' ".
- " AND ( disabled IS NULL OR disabled != 'Y' ) ".
- " AND peo_agentnum.optionname = 'agentnum' ".
- " AND agentnum IS NULL OR agentnum = $agentnum ".
- " ORDER BY
- CASE WHEN peo_cust_bill_age.optionname != 'cust_bill_age'
- THEN -1
- ELSE ". FS::part_event::Condition->age2seconds_sql('peo_cust_bill_age.optionvalue').
- " END
- , part_event.weight".
- " LIMIT 1"
- });
-
- unless ( $part_event_option ) {
- return $self->agent->invoice_template || ''
- if $option eq 'agent_templatename';
- return '';
- }
-
- $part_event_option->optionvalue;
-
-}
-
-sub queued_bill {
- ## actual sub, not a method, designed to be called from the queue.
- ## sets up the customer, and calls the bill_and_collect
- my (%args) = @_; #, ($time, $invoice_time, $check_freq, $resetup) = @_;
- my $cust_main = qsearchs( 'cust_main', { custnum => $args{'custnum'} } );
- $cust_main->bill_and_collect(
- %args,
- );
-}
-
-=back
-
-=head1 BUGS
-
-The delete method.
-
-The delete method should possibly take an FS::cust_main object reference
-instead of a scalar customer number.
-
-Bill and collect options should probably be passed as references instead of a
-list.
-
-There should probably be a configuration file with a list of allowed credit
-card types.
-
-No multiple currency support (probably a larger project than just this module).
-
-payinfo_masked false laziness with cust_pay.pm and cust_refund.pm
-
-Birthdates rely on negative epoch values.
-
-The payby for card/check batches is broken. With mixed batching, bad
-things will happen.
-
-B<collect> I<invoice_time> should be renamed I<time>, like B<bill>.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
-L<FS::agent>, L<FS::part_referral>, L<FS::cust_main_county>,
-L<FS::cust_main_invoice>, L<FS::UID>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm
deleted file mode 100644
index ced0a1f..0000000
--- a/FS/FS/cust_main_Mixin.pm
+++ /dev/null
@@ -1,269 +0,0 @@
-package FS::cust_main_Mixin;
-
-use strict;
-use vars qw( $DEBUG );
-use FS::UID qw(dbh);
-use FS::cust_main;
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::cust_main_Mixin - Mixin class for records that contain fields from cust_main
-
-=head1 SYNOPSIS
-
-package FS::some_table;
-use vars qw(@ISA);
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
-=head1 DESCRIPTION
-
-This is a mixin class for records that contain fields from the cust_main table,
-for example, from a JOINed search. See httemplate/search/ for examples.
-
-=head1 METHODS
-
-=over 4
-
-=item name
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<name> method, or "(unlinked)" if this object is not linked to
-a customer.
-
-=cut
-
-sub cust_unlinked_msg { '(unlinked)'; }
-sub cust_linked { $_[0]->custnum; }
-
-sub name {
- my $self = shift;
- $self->cust_linked
- ? FS::cust_main::name($self)
- : $self->cust_unlinked_msg;
-}
-
-=item ship_name
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<ship_name> method, or "(unlinked)" if this object is not
-linked to a customer.
-
-=cut
-
-sub ship_name {
- my $self = shift;
- $self->cust_linked
- ? FS::cust_main::ship_name($self)
- : $self->cust_unlinked_msg;
-}
-
-=item contact
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<contact> method, or "(unlinked)" if this object is not linked
-to a customer.
-
-=cut
-
-sub contact {
- my $self = shift;
- $self->cust_linked
- ? FS::cust_main::contact($self)
- : $self->cust_unlinked_msg;
-}
-
-=item ship_contact
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<ship_contact> method, or "(unlinked)" if this object is not
-linked to a customer.
-
-=cut
-
-sub ship_contact {
- my $self = shift;
- $self->cust_linked
- ? FS::cust_main::ship_contact($self)
- : $self->cust_unlinked_msg;
-}
-
-=item country_full
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<country_full> method, or "(unlinked)" if this object is not
-linked to a customer.
-
-=cut
-
-sub country_full {
- my $self = shift;
- $self->cust_linked
- ? FS::cust_main::country_full($self)
- : $self->cust_unlinked_msg;
-}
-
-=item invoicing_list_emailonly
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<invoicing_list_emailonly> method, or "(unlinked)" if this
-object is not linked to a customer.
-
-=cut
-
-sub invoicing_list_emailonly {
- my $self = shift;
- warn "invoicing_list_email only called on $self, ".
- "custnum ". $self->custnum. "\n"
- if $DEBUG;
- $self->cust_linked
- ? FS::cust_main::invoicing_list_emailonly($self)
- : $self->cust_unlinked_msg;
-}
-
-=item invoicing_list_emailonly_scalar
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<invoicing_list_emailonly_scalar> method, or "(unlinked)" if
-this object is not linked to a customer.
-
-=cut
-
-sub invoicing_list_emailonly_scalar {
- my $self = shift;
- warn "invoicing_list_emailonly called on $self, ".
- "custnum ". $self->custnum. "\n"
- if $DEBUG;
- $self->cust_linked
- ? FS::cust_main::invoicing_list_emailonly_scalar($self)
- : $self->cust_unlinked_msg;
-}
-
-=item invoicing_list
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<invoicing_list> method, or "(unlinked)" if this object is not
-linked to a customer.
-
-Note: this method is read-only.
-
-=cut
-
-#read-only
-sub invoicing_list {
- my $self = shift;
- $self->cust_linked
- ? FS::cust_main::invoicing_list($self)
- : ();
-}
-
-=item status
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<status> method, or "(unlinked)" if this object is not linked to
-a customer.
-
-=cut
-
-sub cust_status {
- my $self = shift;
- return $self->cust_unlinked_msg unless $self->cust_linked;
-
- #FS::cust_main::status($self)
- #false laziness w/actual cust_main::status
- # (make sure FS::cust_main methods are called)
- for my $status (qw( prospect active inactive suspended cancelled )) {
- my $method = $status.'_sql';
- my $sql = FS::cust_main->$method();;
- my $numnum = ( $sql =~ s/cust_main\.custnum/?/g );
- my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr;
- $sth->execute( ($self->custnum) x $numnum )
- or die "Error executing 'SELECT $sql': ". $sth->errstr;
- return $status if $sth->fetchrow_arrayref->[0];
- }
-}
-
-=item ucfirst_cust_status
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<ucfirst_status> method, or "(unlinked)" if this object is not
-linked to a customer.
-
-=cut
-
-sub ucfirst_cust_status {
- my $self = shift;
- $self->cust_linked
- ? ucfirst( $self->cust_status(@_) )
- : $self->cust_unlinked_msg;
-}
-
-=item cust_statuscolor
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-FS::cust_main I<statuscol> method, or "000000" if this object is not linked to
-a customer.
-
-=cut
-
-sub cust_statuscolor {
- my $self = shift;
-
- $self->cust_linked
- ? FS::cust_main::cust_statuscolor($self)
- : '000000';
-}
-
-=item prospect_sql
-
-=item active_sql
-
-=item inactive_sql
-
-=item suspended_sql
-
-=item cancelled_sql
-
-Given an object that contains fields from cust_main (say, from a JOINed
-search; see httemplate/search/ for examples), returns the equivalent of the
-corresponding FS::cust_main method, or "0" if this object is not linked to
-a customer.
-
-=cut
-
-foreach my $sub (qw( prospect active inactive suspended cancelled )) {
- eval "
- sub ${sub}_sql {
- my \$self = shift;
- \$self->cust_linked
- ? FS::cust_main::${sub}_sql(\$self)
- : '0';
- }
- ";
- die $@ if $@;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::cust_main>, L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm
deleted file mode 100644
index 17f3460..0000000
--- a/FS/FS/cust_main_county.pm
+++ /dev/null
@@ -1,291 +0,0 @@
-package FS::cust_main_county;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK $conf
- @cust_main_county %cust_main_county $countyflag );
-use Exporter;
-use FS::Record qw( qsearch );
-
-@ISA = qw( FS::Record );
-@EXPORT_OK = qw( regionselector );
-
-@cust_main_county = ();
-$countyflag = '';
-
-#ask FS::UID to run this stuff for us later
-$FS::UID::callback{'FS::cust_main_county'} = sub {
- $conf = new FS::Conf;
-};
-
-=head1 NAME
-
-FS::cust_main_county - Object methods for cust_main_county objects
-
-=head1 SYNOPSIS
-
- use FS::cust_main_county;
-
- $record = new FS::cust_main_county \%hash;
- $record = new FS::cust_main_county { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- ($county_html, $state_html, $country_html) =
- FS::cust_main_county::regionselector( $county, $state, $country );
-
-=head1 DESCRIPTION
-
-An FS::cust_main_county object represents a tax rate, defined by locale.
-FS::cust_main_county inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item taxnum - primary key (assigned automatically for new tax rates)
-
-=item state
-
-=item county
-
-=item country
-
-=item tax - percentage
-
-=item taxclass
-
-=item exempt_amount
-
-=item taxname - if defined, printed on invoices instead of "Tax"
-
-=item setuptax - if 'Y', this tax does not apply to setup fees
-
-=item recurtax - if 'Y', this tax does not apply to recurring fees
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new tax rate. To add the tax rate to the database, see L<"insert">.
-
-=cut
-
-sub table { 'cust_main_county'; }
-
-=item insert
-
-Adds this tax rate to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this tax rate from the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid tax rate. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->exempt_amount(0) unless $self->exempt_amount;
-
- $self->ut_numbern('taxnum')
- || $self->ut_anything('state')
- || $self->ut_textn('county')
- || $self->ut_text('country')
- || $self->ut_float('tax')
- || $self->ut_textn('taxclass') # ...
- || $self->ut_money('exempt_amount')
- || $self->ut_textn('taxname')
- || $self->ut_enum('setuptax', [ '', 'Y' ] )
- || $self->ut_enum('recurtax', [ '', 'Y' ] )
- || $self->SUPER::check
- ;
-
-}
-
-sub taxname {
- my $self = shift;
- if ( $self->dbdef_table->column('taxname') ) {
- return $self->setfield('taxname', $_[0]) if @_;
- return $self->getfield('taxname');
- }
- return '';
-}
-
-sub setuptax {
- my $self = shift;
- if ( $self->dbdef_table->column('setuptax') ) {
- return $self->setfield('setuptax', $_[0]) if @_;
- return $self->getfield('setuptax');
- }
- return '';
-}
-
-sub recurtax {
- my $self = shift;
- if ( $self->dbdef_table->column('recurtax') ) {
- return $self->setfield('recurtax', $_[0]) if @_;
- return $self->getfield('recurtax');
- }
- return '';
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item regionselector [ COUNTY STATE COUNTRY [ PREFIX [ ONCHANGE [ DISABLED ] ] ] ]
-
-=cut
-
-sub regionselector {
- my ( $selected_county, $selected_state, $selected_country,
- $prefix, $onchange, $disabled ) = @_;
-
- $prefix = '' unless defined $prefix;
-
- $countyflag = 0;
-
-# unless ( @cust_main_county ) { #cache
- @cust_main_county = qsearch('cust_main_county', {} );
- foreach my $c ( @cust_main_county ) {
- $countyflag=1 if $c->county;
- #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
- $cust_main_county{$c->country}{$c->state}{$c->county} = 1;
- }
-# }
- $countyflag=1 if $selected_county;
-
- my $script_html = <<END;
- <SCRIPT>
- function opt(what,value,text) {
- var optionName = new Option(text, value, false, false);
- var length = what.length;
- what.options[length] = optionName;
- }
- function ${prefix}country_changed(what) {
- country = what.options[what.selectedIndex].text;
- for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
- what.form.${prefix}state.options[i] = null;
-END
- #what.form.${prefix}state.options[0] = new Option('', '', false, true);
-
- foreach my $country ( sort keys %cust_main_county ) {
- $script_html .= "\nif ( country == \"$country\" ) {\n";
- foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
- ( my $dstate = $state ) =~ s/[\n\r]//g;
- my $text = $dstate || '(n/a)';
- $script_html .= qq!opt(what.form.${prefix}state, "$dstate", "$text");\n!;
- }
- $script_html .= "}\n";
- }
-
- $script_html .= <<END;
- }
- function ${prefix}state_changed(what) {
-END
-
- if ( $countyflag ) {
- $script_html .= <<END;
- state = what.options[what.selectedIndex].text;
- country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
- for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
- what.form.${prefix}county.options[i] = null;
-END
-
- foreach my $country ( sort keys %cust_main_county ) {
- $script_html .= "\nif ( country == \"$country\" ) {\n";
- foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
- $script_html .= "\nif ( state == \"$state\" ) {\n";
- #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
- foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
- my $text = $county || '(n/a)';
- $script_html .=
- qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
- }
- $script_html .= "}\n";
- }
- $script_html .= "}\n";
- }
- }
-
- $script_html .= <<END;
- }
- </SCRIPT>
-END
-
- my $county_html = $script_html;
- if ( $countyflag ) {
- $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$onchange" $disabled>!;
- $county_html .= '</SELECT>';
- } else {
- $county_html .=
- qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$selected_county">!;
- }
-
- my $state_html = qq!<SELECT NAME="${prefix}state" !.
- qq!onChange="${prefix}state_changed(this); $onchange" $disabled>!;
- foreach my $state ( sort keys %{ $cust_main_county{$selected_country} } ) {
- my $text = $state || '(n/a)';
- my $selected = $state eq $selected_state ? 'SELECTED' : '';
- $state_html .= qq(\n<OPTION $selected VALUE="$state">$text</OPTION>);
- }
- $state_html .= '</SELECT>';
-
- $state_html .= '</SELECT>';
-
- my $country_html = qq!<SELECT NAME="${prefix}country" !.
- qq!onChange="${prefix}country_changed(this); $onchange" $disabled>!;
- my $countrydefault = $conf->config('countrydefault') || 'US';
- foreach my $country (
- sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
- keys %cust_main_county
- ) {
- my $selected = $country eq $selected_country ? ' SELECTED' : '';
- $country_html .= qq(\n<OPTION$selected VALUE="$country">$country</OPTION>");
- }
- $country_html .= '</SELECT>';
-
- ($county_html, $state_html, $country_html);
-
-}
-
-=back
-
-=head1 BUGS
-
-regionselector? putting web ui components in here? they should probably live
-somewhere else...
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_main_invoice.pm b/FS/FS/cust_main_invoice.pm
deleted file mode 100644
index 71029d0..0000000
--- a/FS/FS/cust_main_invoice.pm
+++ /dev/null
@@ -1,173 +0,0 @@
-package FS::cust_main_invoice;
-
-use strict;
-use vars qw(@ISA $conf);
-use Exporter;
-use FS::Record qw( qsearchs );
-use FS::Conf;
-use FS::cust_main;
-use FS::svc_acct;
-use FS::Msgcat qw(gettext);
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::cust_main_invoice - Object methods for cust_main_invoice records
-
-=head1 SYNOPSIS
-
- use FS::cust_main_invoice;
-
- $record = new FS::cust_main_invoice \%hash;
- $record = new FS::cust_main_invoice { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $email_address = $record->address;
-
-=head1 DESCRIPTION
-
-An FS::cust_main_invoice object represents an invoice destination. FS::cust_main_invoice inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item destnum - primary key
-
-=item custnum - customer (see L<FS::cust_main>)
-
-=item dest - Invoice destination: If numeric, a svcnum (see L<FS::svc_acct>), if string, a literal email address, `POST' to enable mailing (the default if no cust_main_invoice records exist), or `FAX' to enable faxing via a HylaFAX server.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new invoice destination. To add the invoice destination to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'cust_main_invoice'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Delete this record from the database.
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my ( $new, $old ) = ( shift, shift );
-
- return "Can't change custnum!" unless $old->custnum == $new->custnum;
-
- $new->SUPER::replace($old);
-}
-
-
-=item check
-
-Checks all fields to make sure this is a valid invoice destination. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error = $self->ut_numbern('destnum')
- || $self->ut_number('custnum')
- || $self->checkdest;
- ;
- return $error if $error;
-
- return "Unknown customer"
- unless qsearchs('cust_main',{ 'custnum' => $self->custnum });
-
- $self->SUPER::check;
-}
-
-=item checkdest
-
-Checks the dest field only.
-
-#If it finds that the account ends in the
-#same domain configured as the B<domain> configuration file, it will change the
-#invoice destination from an email address to a service number (see
-#L<FS::svc_acct>).
-
-=cut
-
-sub checkdest {
- my $self = shift;
-
- my $error = $self->ut_text('dest');
- return $error if $error;
-
- if ( $self->dest =~ /^(POST|FAX)$/ ) {
- #contemplate our navel
- } elsif ( $self->dest =~ /^(\d+)$/ ) {
- return "Unknown local account (specified by svcnum: ". $self->dest. ")"
- unless qsearchs( 'svc_acct', { 'svcnum' => $self->dest } );
- } elsif ( $self->dest =~ /^([\w\.\-\&\+]+)\@(([\w\.\-]+\.)+\w+)$/ ) {
- my($user, $domain) = ($1, $2);
- $self->dest("$1\@$2");
- } else {
- return gettext("illegal_email_invoice_address"). ': '. $self->dest;
- }
-
- ''; #no error
-}
-
-=item address
-
-Returns the literal email address for this record (or `POST' or `FAX').
-
-=cut
-
-sub address {
- my $self = shift;
- if ( $self->dest =~ /^(\d+)$/ ) {
- my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $1 } )
- or return undef;
- $svc_acct->email;
- } else {
- $self->dest;
- }
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_main>
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_main_note.pm b/FS/FS/cust_main_note.pm
deleted file mode 100644
index 4732d12..0000000
--- a/FS/FS/cust_main_note.pm
+++ /dev/null
@@ -1,131 +0,0 @@
-package FS::cust_main_note;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cust_main_note - Object methods for cust_main_note records
-
-=head1 SYNOPSIS
-
- use FS::cust_main_note;
-
- $record = new FS::cust_main_note \%hash;
- $record = new FS::cust_main_note { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_main_note object represents a note attachted to a customer.
-FS::cust_main_note inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item notenum - primary key
-
-=item custnum -
-
-=item _date -
-
-=item otaker -
-
-=item comments -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new customer note. To add the note to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_main_note'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid example. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('notenum')
- || $self->ut_number('custnum')
- || $self->ut_numbern('_date')
- || $self->ut_text('otaker')
- || $self->ut_anything('comments')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-Lurking in the cracks.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
deleted file mode 100644
index 358dfdc..0000000
--- a/FS/FS/cust_pay.pm
+++ /dev/null
@@ -1,888 +0,0 @@
-package FS::cust_pay;
-
-use strict;
-use vars qw( @ISA $DEBUG $me $conf @encrypted_fields
- $unsuspendauto $ignore_noapply
- );
-use Date::Format;
-use Business::CreditCard;
-use Text::Template;
-use FS::UID qw( getotaker );
-use FS::Misc qw( send_email );
-use FS::Record qw( dbh qsearch qsearchs );
-use FS::payby;
-use FS::cust_main_Mixin;
-use FS::payinfo_Mixin;
-use FS::cust_bill;
-use FS::cust_bill_pay;
-use FS::cust_pay_refund;
-use FS::cust_main;
-use FS::cust_pay_void;
-
-@ISA = qw(FS::Record FS::cust_main_Mixin FS::payinfo_Mixin );
-
-$DEBUG = 0;
-
-$me = '[FS::cust_pay]';
-
-$ignore_noapply = 0;
-
-#ask FS::UID to run this stuff for us later
-FS::UID->install_callback( sub {
- $conf = new FS::Conf;
- $unsuspendauto = $conf->exists('unsuspendauto');
-} );
-
-@encrypted_fields = ('payinfo');
-
-=head1 NAME
-
-FS::cust_pay - Object methods for cust_pay objects
-
-=head1 SYNOPSIS
-
- use FS::cust_pay;
-
- $record = new FS::cust_pay \%hash;
- $record = new FS::cust_pay { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_pay object represents a payment; the transfer of money from a
-customer. FS::cust_pay inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item paynum - primary key (assigned automatically for new payments)
-
-=item custnum - customer (see L<FS::cust_main>)
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item paid - Amount of this payment
-
-=item otaker - order taker (assigned automatically, see L<FS::UID>)
-
-=item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
-
-=item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format)
-
-=item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
-
-=item paybatch - text field for tracking card processing or other batch grouping
-
-=item payunique - Optional unique identifer to prevent duplicate transactions.
-
-=item closed - books closed flag, empty or `Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new payment. To add the payment to the databse, see L<"insert">.
-
-=cut
-
-sub table { 'cust_pay'; }
-sub cust_linked { $_[0]->cust_main_custnum; }
-sub cust_unlinked_msg {
- my $self = shift;
- "WARNING: can't find cust_main.custnum ". $self->custnum.
- ' (cust_pay.paynum '. $self->paynum. ')';
-}
-
-=item insert
-
-Adds this payment to the database.
-
-For backwards-compatibility and convenience, if the additional field invnum
-is defined, an FS::cust_bill_pay record for the full amount of the payment
-will be created. In this case, custnum is optional. An hash of optional
-arguments may be passed. Currently "manual" is supported. If true, a
-payment receipt is sent instead of a statement when 'payment_receipt_email'
-configuration option is set.
-
-=cut
-
-sub insert {
- my ($self, %options) = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $cust_bill;
- if ( $self->invnum ) {
- $cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } )
- or do {
- $dbh->rollback if $oldAutoCommit;
- return "Unknown cust_bill.invnum: ". $self->invnum;
- };
- $self->custnum($cust_bill->custnum );
- }
-
-
- my $error = $self->check;
- return $error if $error;
-
- my $cust_main = $self->cust_main;
- my $old_balance = $cust_main->balance;
-
- $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error inserting $self: $error";
- }
-
- if ( $self->invnum ) {
- my $cust_bill_pay = new FS::cust_bill_pay {
- 'invnum' => $self->invnum,
- 'paynum' => $self->paynum,
- 'amount' => $self->paid,
- '_date' => $self->_date,
- };
- $error = $cust_bill_pay->insert;
- if ( $error ) {
- if ( $ignore_noapply ) {
- warn "warning: error inserting $cust_bill_pay: $error ".
- "(ignore_noapply flag set; inserting cust_pay record anyway)\n";
- } else {
- $dbh->rollback if $oldAutoCommit;
- return "error inserting $cust_bill_pay: $error";
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- #false laziness w/ cust_credit::insert
- if ( $unsuspendauto && $old_balance && $cust_main->balance <= 0 ) {
- my @errors = $cust_main->unsuspend;
- #return
- # side-fx with nested transactions? upstack rolls back?
- warn "WARNING:Errors unsuspending customer ". $cust_main->custnum. ": ".
- join(' / ', @errors)
- if @errors;
- }
- #eslaf
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- #my $cust_main = $self->cust_main;
- if ( $conf->exists('payment_receipt_email')
- && grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list
- ) {
-
- $cust_bill ||= ($cust_main->cust_bill)[-1]; #rather inefficient though?
-
- my $error;
- if ( ( exists($options{'manual'}) && $options{'manual'} )
- || ! $conf->exists('invoice_html_statement')
- || ! $cust_bill
- ) {
-
- my $receipt_template = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", $conf->config('payment_receipt_email') ],
- ) or do {
- warn "can't create payment receipt template: $Text::Template::ERROR";
- return '';
- };
-
- my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ }
- $cust_main->invoicing_list;
-
- my $payby = $self->payby;
- my $payinfo = $self->payinfo;
- $payby =~ s/^BILL$/Check/ if $payinfo;
- $payinfo = $self->paymask if $payby eq 'CARD' || $payby eq 'CHEK';
- $payby =~ s/^CHEK$/Electronic check/;
-
- $error = send_email(
- 'from' => $conf->config('invoice_from'), #??? well as good as any
- 'to' => \@invoicing_list,
- 'subject' => 'Payment receipt',
- 'body' => [ $receipt_template->fill_in( HASH => {
- 'date' => time2str("%a %B %o, %Y", $self->_date),
- 'name' => $cust_main->name,
- 'paynum' => $self->paynum,
- 'paid' => sprintf("%.2f", $self->paid),
- 'payby' => ucfirst(lc($payby)),
- 'payinfo' => $payinfo,
- 'balance' => $cust_main->balance,
- } ) ],
- );
-
- } else {
-
- my $queue = new FS::queue {
- 'paynum' => $self->paynum,
- 'job' => 'FS::cust_bill::queueable_email',
- };
- $error = $queue->insert(
- 'invnum' => $cust_bill->invnum,
- 'template' => 'statement',
- );
-
- }
-
- if ( $error ) {
- warn "can't send payment receipt/statement: $error";
- }
-
- }
-
- '';
-
-}
-
-=item void [ REASON ]
-
-Voids this payment: deletes the payment and all associated applications and
-adds a record of the voided payment to the FS::cust_pay_void table.
-
-=cut
-
-sub void {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $cust_pay_void = new FS::cust_pay_void ( {
- map { $_ => $self->get($_) } $self->fields
- } );
- $cust_pay_void->reason(shift) if scalar(@_);
- my $error = $cust_pay_void->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $error = $self->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-Unless the closed flag is set, deletes this payment and all associated
-applications (see L<FS::cust_bill_pay> and L<FS::cust_pay_refund>). In most
-cases, you want to use the void method instead to leave a record of the
-deleted payment.
-
-=cut
-
-# very similar to FS::cust_credit::delete
-sub delete {
- my $self = shift;
- return "Can't delete closed payment" if $self->closed =~ /^Y/i;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $app ( $self->cust_bill_pay, $self->cust_pay_refund ) {
- my $error = $app->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $error = $self->SUPER::delete(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $conf->config('deletepayments') ne '' ) {
-
- my $cust_main = $self->cust_main;
-
- my $error = send_email(
- 'from' => $conf->config('invoice_from'), #??? well as good as any
- 'to' => $conf->config('deletepayments'),
- 'subject' => 'FREESIDE NOTIFICATION: Payment deleted',
- 'body' => [
- "This is an automatic message from your Freeside installation\n",
- "informing you that the following payment has been deleted:\n",
- "\n",
- 'paynum: '. $self->paynum. "\n",
- 'custnum: '. $self->custnum.
- " (". $cust_main->last. ", ". $cust_main->first. ")\n",
- 'paid: $'. sprintf("%.2f", $self->paid). "\n",
- 'date: '. time2str("%a %b %e %T %Y", $self->_date). "\n",
- 'payby: '. $self->payby. "\n",
- 'payinfo: '. $self->paymask. "\n",
- 'paybatch: '. $self->paybatch. "\n",
- ],
- );
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't send payment deletion notification: $error";
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item replace OLD_RECORD
-
-You can, but probably shouldn't modify payments...
-
-=cut
-
-sub replace {
- #return "Can't modify payment!"
- my $self = shift;
- return "Can't modify closed payment" if $self->closed =~ /^Y/i;
- $self->SUPER::replace(@_);
-}
-
-=item check
-
-Checks all fields to make sure this is a valid payment. If there is an error,
-returns the error, otherwise returns false. Called by the insert method.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->otaker(getotaker) unless ($self->otaker);
-
- my $error =
- $self->ut_numbern('paynum')
- || $self->ut_numbern('custnum')
- || $self->ut_numbern('_date')
- || $self->ut_money('paid')
- || $self->ut_alpha('otaker')
- || $self->ut_textn('paybatch')
- || $self->ut_textn('payunique')
- || $self->ut_enum('closed', [ '', 'Y' ])
- || $self->payinfo_check()
- ;
- return $error if $error;
-
- return "paid must be > 0 " if $self->paid <= 0;
-
- return "unknown cust_main.custnum: ". $self->custnum
- unless $self->invnum
- || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-
- $self->_date(time) unless $self->_date;
-
-#i guess not now, with cust_pay_pending, if we actually make it here, we _do_ want to record it
-# # UNIQUE index should catch this too, without race conditions, but this
-# # should give a better error message the other 99.9% of the time...
-# if ( length($self->payunique)
-# && qsearchs('cust_pay', { 'payunique' => $self->payunique } ) ) {
-# #well, it *could* be a better error message
-# return "duplicate transaction".
-# " - a payment with unique identifer ". $self->payunique.
-# " already exists";
-# }
-
- $self->SUPER::check;
-}
-
-=item batch_insert CUST_PAY_OBJECT, ...
-
-Class method which inserts multiple payments. Takes a list of FS::cust_pay
-objects. Returns a list, each element representing the status of inserting the
-corresponding payment - empty. If there is an error inserting any payment, the
-entire transaction is rolled back, i.e. all payments are inserted or none are.
-
-For example:
-
- my @errors = FS::cust_pay->batch_insert(@cust_pay);
- my $num_errors = scalar(grep $_, @errors);
- if ( $num_errors == 0 ) {
- #success; all payments were inserted
- } else {
- #failure; no payments were inserted.
- }
-
-=cut
-
-sub batch_insert {
- my $self = shift; #class method
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $errors = 0;
-
- my @errors = map {
- my $error = $_->insert( 'manual' => 1 );
- if ( $error ) {
- $errors++;
- } else {
- $_->cust_main->apply_payments;
- }
- $error;
- } @_;
-
- if ( $errors ) {
- $dbh->rollback if $oldAutoCommit;
- } else {
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- }
-
- @errors;
-
-}
-
-=item cust_bill_pay
-
-Returns all applications to invoices (see L<FS::cust_bill_pay>) for this
-payment.
-
-=cut
-
-sub cust_bill_pay {
- my $self = shift;
- sort { $a->_date <=> $b->_date
- || $a->invnum <=> $b->invnum }
- qsearch( 'cust_bill_pay', { 'paynum' => $self->paynum } )
- ;
-}
-
-=item cust_pay_refund
-
-Returns all applications of refunds (see L<FS::cust_pay_refund>) to this
-payment.
-
-=cut
-
-sub cust_pay_refund {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_pay_refund', { 'paynum' => $self->paynum } )
- ;
-}
-
-
-=item unapplied
-
-Returns the amount of this payment that is still unapplied; which is
-paid minus all payment applications (see L<FS::cust_bill_pay>) and refund
-applications (see L<FS::cust_pay_refund>).
-
-=cut
-
-sub unapplied {
- my $self = shift;
- my $amount = $self->paid;
- $amount -= $_->amount foreach ( $self->cust_bill_pay );
- $amount -= $_->amount foreach ( $self->cust_pay_refund );
- sprintf("%.2f", $amount );
-}
-
-=item unrefunded
-
-Returns the amount of this payment that has not been refuned; which is
-paid minus all refund applications (see L<FS::cust_pay_refund>).
-
-=cut
-
-sub unrefunded {
- my $self = shift;
- my $amount = $self->paid;
- $amount -= $_->amount foreach ( $self->cust_pay_refund );
- sprintf("%.2f", $amount );
-}
-
-
-=item cust_main
-
-Returns the parent customer object (see L<FS::cust_main>).
-
-=cut
-
-sub cust_main {
- my $self = shift;
- qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-}
-
-=item payby_name
-
-Returns a name for the payby field.
-
-=cut
-
-sub payby_name {
- my $self = shift;
- FS::payby->shortname( $self->payby );
-}
-
-=item gatewaynum
-
-Returns a gatewaynum for the processing gateway.
-
-=item processor
-
-Returns a name for the processing gateway.
-
-=item authorization
-
-Returns a name for the processing gateway.
-
-=item order_number
-
-Returns a name for the processing gateway.
-
-=cut
-
-sub gatewaynum { shift->_parse_paybatch->{'gatewaynum'}; }
-sub processor { shift->_parse_paybatch->{'processor'}; }
-sub authorization { shift->_parse_paybatch->{'authorization'}; }
-sub order_number { shift->_parse_paybatch->{'order_number'}; }
-
-#sucks that this stuff is in paybatch like this in the first place,
-#but at least other code can start to use new field names
-#(code nicked from FS::cust_main::realtime_refund_bop)
-sub _parse_paybatch {
- my $self = shift;
-
- $self->paybatch =~ /^((\d+)\-)?(\w+):\s*([\w\-\/ ]*)(:([\w\-]+))?$/
- or return {};
- #"Can't parse paybatch for paynum $options{'paynum'}: ".
- # $cust_pay->paybatch;
-
- my( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 );
-
- if ( $gatewaynum ) { #gateway for the payment to be refunded
-
- my $payment_gateway =
- qsearchs('payment_gateway', { 'gatewaynum' => $gatewaynum } );
-
- die "payment gateway $gatewaynum not found" #?
- unless $payment_gateway;
-
- $processor = $payment_gateway->gateway_module;
-
- }
-
- {
- 'gatewaynum' => $gatewaynum,
- 'processor' => $processor,
- 'authorization' => $auth,
- 'order_number' => $order_number,
- };
-
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item unapplied_sql
-
-Returns an SQL fragment to retreive the unapplied amount.
-
-=cut
-
-sub unapplied_sql {
- #my $class = shift;
-
- "paid
- - COALESCE(
- ( SELECT SUM(amount) FROM cust_bill_pay
- WHERE cust_pay.paynum = cust_bill_pay.paynum )
- ,0
- )
- - COALESCE(
- ( SELECT SUM(amount) FROM cust_pay_refund
- WHERE cust_pay.paynum = cust_pay_refund.paynum )
- ,0
- )
- ";
-
-}
-
-# _upgrade_data
-#
-# Used by FS::Upgrade to migrate to a new database.
-
-use FS::h_cust_pay;
-
-sub _upgrade_data { #class method
- my ($class, %opts) = @_;
-
- warn "$me upgrading $class\n" if $DEBUG;
-
- #not the most efficient, but hey, it only has to run once
-
- my $where = "WHERE ( otaker IS NULL OR otaker = '' OR otaker = 'ivan' ) ".
- " AND 0 < ( SELECT COUNT(*) FROM cust_main ".
- " WHERE cust_main.custnum = cust_pay.custnum ) ";
-
- my $count_sql = "SELECT COUNT(*) FROM cust_pay $where";
-
- my $sth = dbh->prepare($count_sql) or die dbh->errstr;
- $sth->execute or die $sth->errstr;
- my $total = $sth->fetchrow_arrayref->[0];
-
- local($DEBUG) = 2 if $total > 1000; #could be a while, force progress info
-
- my $count = 0;
- my $lastprog = 0;
-
- my @cust_pay = qsearch( {
- 'table' => 'cust_pay',
- 'hashref' => {},
- 'extra_sql' => $where,
- 'order_by' => 'ORDER BY paynum',
- } );
-
- foreach my $cust_pay (@cust_pay) {
-
- my $h_cust_pay = $cust_pay->h_search('insert');
- if ( $h_cust_pay ) {
- $cust_pay->otaker($h_cust_pay->history_user);
- } else {
- $cust_pay->otaker('legacy');
- }
-
- delete $FS::payby::hash{'COMP'}->{cust_pay}; #quelle kludge
- my $error = $cust_pay->replace;
-
- if ( $error ) {
- warn " *** WARNING: Error updaating order taker for payment paynum".
- $cust_pay->paynun. ": $error\n";
- next;
- }
-
- $FS::payby::hash{'COMP'}->{cust_pay} = ''; #restore it
-
- $count++;
- if ( $DEBUG > 1 && $lastprog + 30 < time ) {
- warn "$me $count/$total (". sprintf('%.2f',100*$count/$total). '%)'. "\n";
- $lastprog = time;
- }
-
- }
-
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item batch_import HASHREF
-
-Inserts new payments.
-
-=cut
-
-sub batch_import {
- my $param = shift;
-
- my $fh = $param->{filehandle};
- my $agentnum = $param->{agentnum};
- my $format = $param->{'format'};
- my $paybatch = $param->{'paybatch'};
-
- # here is the agent virtualization
- my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql;
-
- my @fields;
- my $payby;
- if ( $format eq 'simple' ) {
- @fields = qw( custnum agent_custid paid payinfo );
- $payby = 'BILL';
- } elsif ( $format eq 'extended' ) {
- die "unimplemented\n";
- @fields = qw( );
- $payby = 'BILL';
- } else {
- die "unknown format $format";
- }
-
- eval "use Text::CSV_XS;";
- die $@ if $@;
-
- my $csv = new Text::CSV_XS;
-
- my $imported = 0;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $line;
- while ( defined($line=<$fh>) ) {
-
- $csv->parse($line) or do {
- $dbh->rollback if $oldAutoCommit;
- return "can't parse: ". $csv->error_input();
- };
-
- my @columns = $csv->fields();
-
- my %cust_pay = (
- payby => $payby,
- paybatch => $paybatch,
- );
-
- my $cust_main;
- foreach my $field ( @fields ) {
-
- if ( $field eq 'agent_custid'
- && $agentnum
- && $columns[0] =~ /\S+/ )
- {
-
- my $agent_custid = $columns[0];
- my %hash = ( 'agent_custid' => $agent_custid,
- 'agentnum' => $agentnum,
- );
-
- if ( $cust_pay{'custnum'} !~ /^\s*$/ ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't specify custnum with agent_custid $agent_custid";
- }
-
- $cust_main = qsearchs({
- 'table' => 'cust_main',
- 'hashref' => \%hash,
- 'extra_sql' => $extra_sql,
- });
-
- unless ( $cust_main ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't find customer with agent_custid $agent_custid";
- }
-
- $field = 'custnum';
- $columns[0] = $cust_main->custnum;
- }
-
- $cust_pay{$field} = shift @columns;
- }
-
- my $cust_pay = new FS::cust_pay( \%cust_pay );
- my $error = $cust_pay->insert;
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't insert payment for $line: $error";
- }
-
- if ( $format eq 'simple' ) {
- # include agentnum for less surprise?
- $cust_main = qsearchs({
- 'table' => 'cust_main',
- 'hashref' => { 'custnum' => $cust_pay->custnum },
- 'extra_sql' => $extra_sql,
- })
- unless $cust_main;
-
- unless ( $cust_main ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't find customer to which payments apply at line: $line";
- }
-
- $error = $cust_main->apply_payments_and_credits;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't apply payments to customer for $line: $error";
- }
-
- }
-
- $imported++;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- return "Empty file!" unless $imported;
-
- ''; #no error
-
-}
-
-=back
-
-=head1 BUGS
-
-Delete and replace methods.
-
-=head1 SEE ALSO
-
-L<FS::cust_pay_pending>, L<FS::cust_bill_pay>, L<FS::cust_bill>, L<FS::Record>,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm
deleted file mode 100644
index 9ef1e1c..0000000
--- a/FS/FS/cust_pay_batch.pm
+++ /dev/null
@@ -1,277 +0,0 @@
-package FS::cust_pay_batch;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Carp qw( confess );
-use Business::CreditCard 0.28;
-use FS::Record qw(dbh qsearch qsearchs);
-use FS::payinfo_Mixin;
-use FS::cust_main;
-use FS::cust_bill;
-
-@ISA = qw( FS::payinfo_Mixin FS::Record );
-
-# 1 is mostly method/subroutine entry and options
-# 2 traces progress of some operations
-# 3 is even more information including possibly sensitive data
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::cust_pay_batch - Object methods for batch cards
-
-=head1 SYNOPSIS
-
- use FS::cust_pay_batch;
-
- $record = new FS::cust_pay_batch \%hash;
- $record = new FS::cust_pay_batch { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- #deprecated# $error = $record->retriable;
-
-=head1 DESCRIPTION
-
-An FS::cust_pay_batch object represents a credit card transaction ready to be
-batched (sent to a processor). FS::cust_pay_batch inherits from FS::Record.
-Typically called by the collect method of an FS::cust_main object. The
-following fields are currently supported:
-
-=over 4
-
-=item paybatchnum - primary key (automatically assigned)
-
-=item batchnum - indentifies group in batch
-
-=item payby - CARD/CHEK/LECB/BILL/COMP
-
-=item payinfo
-
-=item exp - card expiration
-
-=item amount
-
-=item invnum - invoice
-
-=item custnum - customer
-
-=item payname - name on card
-
-=item first - name
-
-=item last - name
-
-=item address1
-
-=item address2
-
-=item city
-
-=item state
-
-=item zip
-
-=item country
-
-=item status
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'cust_pay_batch'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Delete this record from the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid transaction. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('paybatchnum')
- || $self->ut_numbern('trancode') #deprecated
- || $self->ut_money('amount')
- || $self->ut_number('invnum')
- || $self->ut_number('custnum')
- || $self->ut_text('address1')
- || $self->ut_textn('address2')
- || $self->ut_text('city')
- || $self->ut_textn('state')
- ;
-
- return $error if $error;
-
- $self->getfield('last') =~ /^([\w \,\.\-\']+)$/ or return "Illegal last name";
- $self->setfield('last',$1);
-
- $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name";
- $self->first($1);
-
- $error = $self->payinfo_check();
- return $error if $error;
-
- if ( $self->exp eq '' ) {
- return "Expiration date required"
- unless $self->payby =~ /^(CHEK|DCHK|LECB|WEST)$/;
- $self->exp('');
- } else {
- if ( $self->exp =~ /^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/ ) {
- $self->exp("$1-$2-$3");
- } elsif ( $self->exp =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/ ) {
- if ( length($2) == 4 ) {
- $self->exp("$2-$1-01");
- } elsif ( $2 > 98 ) { #should pry change to check for "this year"
- $self->exp("19$2-$1-01");
- } else {
- $self->exp("20$2-$1-01");
- }
- } else {
- return "Illegal expiration date";
- }
- }
-
- if ( $self->payname eq '' ) {
- $self->payname( $self->first. " ". $self->getfield('last') );
- } else {
- $self->payname =~ /^([\w \,\.\-\']+)$/
- or return "Illegal billing name";
- $self->payname($1);
- }
-
- #we have lots of old zips in there... don't hork up batch results cause of em
- $self->zip =~ /^\s*(\w[\w\-\s]{2,8}\w)\s*$/
- or return "Illegal zip: ". $self->zip;
- $self->zip($1);
-
- $self->country =~ /^(\w\w)$/ or return "Illegal country: ". $self->country;
- $self->country($1);
-
- #$error = $self->ut_zip('zip', $self->country);
- #return $error if $error;
-
- #check invnum, custnum, ?
-
- $self->SUPER::check;
-}
-
-=item cust_main
-
-Returns the customer (see L<FS::cust_main>) for this batched credit card
-payment.
-
-=cut
-
-sub cust_main {
- my $self = shift;
- qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-}
-
-#you know what, screw this in the new world of events. we should be able to
-#get the event defs to retry (remove once.pm condition, add every.pm) without
-#mucking about with statuses of previous cust_event records. right?
-#
-#=item retriable
-#
-#Marks the corresponding event (see L<FS::cust_bill_event>) for this batched
-#credit card payment as retriable. Useful if the corresponding financial
-#institution account was declined for temporary reasons and/or a manual
-#retry is desired.
-#
-#Implementation details: For the named customer's invoice, changes the
-#statustext of the 'done' (without statustext) event to 'retriable.'
-#
-#=cut
-
-sub retriable {
-
- confess "deprecated method cust_pay_batch->retriable called; try removing ".
- "the once condition and adding an every condition?";
-
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE'; #Hmm
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } )
- or return "event $self->eventnum references nonexistant invoice $self->invnum";
-
- warn "cust_pay_batch->retriable working with self of " . $self->paybatchnum . " and invnum of " . $self->invnum;
- my @cust_bill_event =
- sort { $a->part_bill_event->seconds <=> $b->part_bill_event->seconds }
- grep {
- $_->part_bill_event->eventcode =~ /\$cust_bill->batch_card/
- && $_->status eq 'done'
- && ! $_->statustext
- }
- $cust_bill->cust_bill_event;
- # complain loudly if scalar(@cust_bill_event) > 1 ?
- my $error = $cust_bill_event[0]->retriable;
- if ($error ) {
- # gah, even with transactions.
- $dbh->commit if $oldAutoCommit; #well.
- return "error marking invoice event retriable: $error";
- }
- '';
-}
-
-=back
-
-=head1 BUGS
-
-There should probably be a configuration file with a list of allowed credit
-card types.
-
-=head1 SEE ALSO
-
-L<FS::cust_main>, L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pay_pending.pm b/FS/FS/cust_pay_pending.pm
deleted file mode 100644
index ad39b10..0000000
--- a/FS/FS/cust_pay_pending.pm
+++ /dev/null
@@ -1,229 +0,0 @@
-package FS::cust_pay_pending;
-
-use strict;
-use vars qw( @ISA @encrypted_fields );
-use FS::Record qw( qsearch qsearchs );
-use FS::payby;
-use FS::payinfo_Mixin;
-use FS::cust_main;
-use FS::cust_pay;
-
-@ISA = qw(FS::Record FS::payinfo_Mixin);
-
-@encrypted_fields = ('payinfo');
-
-=head1 NAME
-
-FS::cust_pay_pending - Object methods for cust_pay_pending records
-
-=head1 SYNOPSIS
-
- use FS::cust_pay_pending;
-
- $record = new FS::cust_pay_pending \%hash;
- $record = new FS::cust_pay_pending { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_pay_pending object represents an pending payment. It reflects
-local state through the multiple stages of processing a real-time transaction
-with an external gateway. FS::cust_pay_pending inherits from FS::Record. The
-following fields are currently supported:
-
-=over 4
-
-=item paypendingnum
-
-Primary key
-
-=item custnum
-
-Customer (see L<FS::cust_main>)
-
-=item paid
-
-Amount of this payment
-
-=item _date
-
-Specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item payby
-
-Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
-
-=item payinfo
-
-Payment Information (See L<FS::payinfo_Mixin> for data format)
-
-=item paymask
-
-Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
-
-=item paydate
-
-Expiration date
-
-=item payunique
-
-Unique identifer to prevent duplicate transactions.
-
-=item status
-
-Pending transaction status, one of the following:
-
-=over 4
-
-=item new
-
-Aquires basic lock on payunique
-
-=item pending
-
-Transaction is pending with the gateway
-
-=item authorized
-
-Only used for two-stage transactions that require a separate capture step
-
-=item captured
-
-Transaction completed with payment gateway (sucessfully), not yet recorded in
-the database
-
-=item declined
-
-Transaction completed with payment gateway (declined), not yet recorded in
-the database
-
-=item done
-
-Transaction recorded in database
-
-=back
-
-=item statustext
-
-Additional status information.
-
-=cut
-
-#=item cust_balance -
-
-=item paynum -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new pending payment. To add the pending payment to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_pay_pending'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid pending payment. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('paypendingnum')
- || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
- || $self->ut_money('paid')
- || $self->ut_numbern('_date')
- || $self->ut_textn('payunique')
- || $self->ut_text('status')
- #|| $self->ut_textn('statustext')
- || $self->ut_anything('statustext')
- #|| $self->ut_money('cust_balance')
- || $self->ut_foreign_keyn('paynum', 'cust_pay', 'paynum' )
- || $self->payinfo_check() #payby/payinfo/paymask/paydate
- ;
- return $error if $error;
-
- $self->_date(time) unless $self->_date;
-
- # UNIQUE index should catch this too, without race conditions, but this
- # should give a better error message the other 99.9% of the time...
- if ( length($self->payunique) ) {
- my $cust_pay_pending = qsearchs('cust_pay_pending', {
- 'payunique' => $self->payunique,
- 'paypendingnum' => { op=>'!=', value=>$self->paypendingnum },
- });
- if ( $cust_pay_pending ) {
- #well, it *could* be a better error message
- return "duplicate transaction - a payment with unique identifer ".
- $self->payunique. " already exists";
- }
- }
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pay_refund.pm b/FS/FS/cust_pay_refund.pm
deleted file mode 100644
index cb9dbce..0000000
--- a/FS/FS/cust_pay_refund.pm
+++ /dev/null
@@ -1,188 +0,0 @@
-package FS::cust_pay_refund;
-
-use strict;
-use vars qw( @ISA ); #$conf );
-use FS::UID qw( getotaker );
-use FS::Record qw( qsearchs ); # qsearch );
-use FS::cust_main;
-use FS::cust_pay;
-use FS::cust_refund;
-
-@ISA = qw( FS::Record );
-
-#ask FS::UID to run this stuff for us later
-#FS::UID->install_callback( sub {
-# $conf = new FS::Conf;
-#} );
-
-=head1 NAME
-
-FS::cust_pay_refund - Object methods for cust_pay_refund records
-
-=head1 SYNOPSIS
-
- use FS::cust_pay_refund;
-
- $record = new FS::cust_pay_refund \%hash;
- $record = new FS::cust_pay_refund { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_pay_refund object represents application of a refund (see
-L<FS::cust_refund>) to an payment (see L<FS::cust_pay>). FS::cust_pay_refund
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item payrefundnum - primary key
-
-=item paynum - credit being applied
-
-=item refundnum - invoice to which credit is applied (see L<FS::cust_bill>)
-
-=item amount - amount of the credit applied
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new cust_pay_refund. To add the cust_pay_refund to the database,
-see L<"insert">.
-
-=cut
-
-sub table { 'cust_pay_refund'; }
-
-=item insert
-
-Adds this cust_pay_refund to the database. If there is an error, returns the
-error, otherwise returns false.
-
-=cut
-
-sub insert {
- my $self = shift;
- return "Can't apply refund to closed payment"
- if $self->cust_pay->closed =~ /^Y/i;
- return "Can't apply payment to closed refund"
- if $self->cust_refund->closed =~ /^Y/i;
- $self->SUPER::insert(@_);
-}
-
-=item delete
-
-=cut
-
-sub delete {
- my $self = shift;
- return "Can't remove refund from closed payment"
- if $self->cust_pay->closed =~ /^Y/i;
- return "Can't remove payment from closed refund"
- if $self->cust_refund->closed =~ /^Y/i;
- $self->SUPER::delete(@_);
-}
-
-=item replace OLD_RECORD
-
-Application of refunds to payments may not be modified.
-
-=cut
-
-sub replace {
- return "Can't modify application of a refund to payment!"
-}
-
-=item check
-
-Checks all fields to make sure this is a valid refund application to a payment.
-If there is an error, returns the error, otherwise returns false. Called by
-the insert and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('payrefundnum')
- || $self->ut_number('paynum')
- || $self->ut_number('refundnum')
- || $self->ut_numbern('_date')
- || $self->ut_money('amount')
- ;
- return $error if $error;
-
- return "amount must be > 0" if $self->amount <= 0;
-
- return "Unknown payment"
- unless my $cust_pay =
- qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
-
- return "Unknown refund"
- unless my $cust_refund =
- qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } );
-
- $self->_date(time) unless $self->_date;
-
- return 'Cannot apply ($'. $self->amount. ') more than'.
- ' remaining value of refund ($'. $cust_refund->unapplied. ')'
- unless $self->amount <= $cust_refund->unapplied;
-
- return "Cannot apply more than remaining value of payment"
- unless $self->amount <= $cust_pay->unapplied;
-
- $self->SUPER::check;
-}
-
-=item sub cust_pay
-
-Returns the payment (see L<FS::cust_pay>)
-
-=cut
-
-sub cust_pay {
- my $self = shift;
- qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
-}
-
-=item cust_refund
-
-Returns the refund (see L<FS::cust_refund>)
-
-=cut
-
-sub cust_refund {
- my $self = shift;
- qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } );
-}
-
-=back
-
-=head1 BUGS
-
-The delete method.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_refund>, L<FS::cust_bill>, L<FS::cust_credit>,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pay_void.pm b/FS/FS/cust_pay_void.pm
deleted file mode 100644
index de05f71..0000000
--- a/FS/FS/cust_pay_void.pm
+++ /dev/null
@@ -1,225 +0,0 @@
-package FS::cust_pay_void;
-use strict;
-use vars qw( @ISA @encrypted_fields );
-use Business::CreditCard;
-use FS::UID qw(getotaker);
-use FS::Record qw(qsearchs dbh fields); # qsearch );
-use FS::cust_pay;
-#use FS::cust_bill;
-#use FS::cust_bill_pay;
-#use FS::cust_pay_refund;
-#use FS::cust_main;
-
-@ISA = qw( FS::Record FS::payinfo_Mixin );
-
-@encrypted_fields = ('payinfo');
-
-=head1 NAME
-
-FS::cust_pay_void - Object methods for cust_pay_void objects
-
-=head1 SYNOPSIS
-
- use FS::cust_pay_void;
-
- $record = new FS::cust_pay_void \%hash;
- $record = new FS::cust_pay_void { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_pay_void object represents a voided payment. The following fields
-are currently supported:
-
-=over 4
-
-=item paynum - primary key (assigned automatically for new payments)
-
-=item custnum - customer (see L<FS::cust_main>)
-
-=item paid - Amount of this payment
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item payby - `CARD' (credit cards), `CHEK' (electronic check/ACH),
-`LECB' (phone bill billing), `BILL' (billing), `CASH' (cash),
-`WEST' (Western Union), `MCRD' (Manual credit card), or `COMP' (free)
-
-=item payinfo - card number, check #, or comp issuer (4-8 lowercase alphanumerics; think username), respectively
-
-=item paybatch - text field for tracking card processing
-
-=item closed - books closed flag, empty or `Y'
-
-=item void_date
-
-=item reason
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new payment. To add the payment to the databse, see L<"insert">.
-
-=cut
-
-sub table { 'cust_pay_void'; }
-
-=item insert
-
-Adds this voided payment to the database.
-
-=item unvoid
-
-"Un-void"s this payment: Deletes the voided payment from the database and adds
-back a normal payment.
-
-=cut
-
-sub unvoid {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $cust_pay = new FS::cust_pay ( {
- map { $_ => $self->get($_) } fields('cust_pay')
- } );
- my $error = $cust_pay->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $error = $self->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-Deletes this voided payment. You probably don't want to use this directly; see
-the B<unvoid> method to add the original payment back.
-
-=item replace OLD_RECORD
-
-Currently unimplemented.
-
-=cut
-
-sub replace {
- return "Can't modify voided payments!";
-}
-
-=item check
-
-Checks all fields to make sure this is a valid voided payment. If there is an
-error, returns the error, otherwise returns false. Called by the insert
-method.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('paynum')
- || $self->ut_numbern('custnum')
- || $self->ut_money('paid')
- || $self->ut_number('_date')
- || $self->ut_textn('paybatch')
- || $self->ut_enum('closed', [ '', 'Y' ])
- || $self->ut_numbern('void_date')
- || $self->ut_textn('reason')
- ;
- return $error if $error;
-
- return "paid must be > 0 " if $self->paid <= 0;
-
- return "unknown cust_main.custnum: ". $self->custnum
- unless $self->invnum
- || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-
- $self->void_date(time) unless $self->void_date;
-
- $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP|PREP|CASH|WEST|MCRD)$/
- or return "Illegal payby";
- $self->payby($1);
-
- #false laziness with cust_refund::check
- if ( $self->payby eq 'CARD' ) {
- my $payinfo = $self->payinfo;
- $payinfo =~ s/\D//g;
- $self->payinfo($payinfo);
- if ( $self->payinfo ) {
- $self->payinfo =~ /^(\d{13,16})$/
- or return "Illegal (mistyped?) credit card number (payinfo)";
- $self->payinfo($1);
- validate($self->payinfo) or return "Illegal credit card number";
- return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
- } else {
- $self->payinfo('N/A');
- }
-
- } else {
- $error = $self->ut_textn('payinfo');
- return $error if $error;
- }
-
- $self->otaker(getotaker);
-
- $self->SUPER::check;
-}
-
-=item cust_main
-
-Returns the parent customer object (see L<FS::cust_main>).
-
-=cut
-
-sub cust_main {
- my $self = shift;
- qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-}
-
-=back
-
-=head1 BUGS
-
-Delete and replace methods.
-
-=head1 SEE ALSO
-
-L<FS::cust_pay>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
deleted file mode 100644
index d413596..0000000
--- a/FS/FS/cust_pkg.pm
+++ /dev/null
@@ -1,2091 +0,0 @@
-package FS::cust_pkg;
-
-use strict;
-use vars qw(@ISA $disable_agentcheck $DEBUG);
-use List::Util qw(max);
-use Tie::IxHash;
-use FS::UID qw( getotaker dbh );
-use FS::Misc qw( send_email );
-use FS::Record qw( qsearch qsearchs );
-use FS::m2m_Common;
-use FS::cust_main_Mixin;
-use FS::cust_svc;
-use FS::part_pkg;
-use FS::cust_main;
-use FS::type_pkgs;
-use FS::pkg_svc;
-use FS::cust_bill_pkg;
-use FS::cust_event;
-use FS::h_cust_svc;
-use FS::reg_code;
-use FS::part_svc;
-use FS::cust_pkg_reason;
-use FS::reason;
-use FS::UI::Web;
-
-# need to 'use' these instead of 'require' in sub { cancel, suspend, unsuspend,
-# setup }
-# because they load configuration by setting FS::UID::callback (see TODO)
-use FS::svc_acct;
-use FS::svc_domain;
-use FS::svc_www;
-use FS::svc_forward;
-
-# for sending cancel emails in sub cancel
-use FS::Conf;
-
-@ISA = qw( FS::m2m_Common FS::cust_main_Mixin FS::option_Common FS::Record );
-
-$DEBUG = 0;
-
-$disable_agentcheck = 0;
-
-sub _cache {
- my $self = shift;
- my ( $hashref, $cache ) = @_;
- #if ( $hashref->{'pkgpart'} ) {
- if ( $hashref->{'pkg'} ) {
- # #@{ $self->{'_pkgnum'} } = ();
- # my $subcache = $cache->subcache('pkgpart', 'part_pkg');
- # $self->{'_pkgpart'} = $subcache;
- # #push @{ $self->{'_pkgnum'} },
- # FS::part_pkg->new_or_cached($hashref, $subcache);
- $self->{'_pkgpart'} = FS::part_pkg->new($hashref);
- }
- if ( exists $hashref->{'svcnum'} ) {
- #@{ $self->{'_pkgnum'} } = ();
- my $subcache = $cache->subcache('svcnum', 'cust_svc', $hashref->{pkgnum});
- $self->{'_svcnum'} = $subcache;
- #push @{ $self->{'_pkgnum'} },
- FS::cust_svc->new_or_cached($hashref, $subcache) if $hashref->{svcnum};
- }
-}
-
-=head1 NAME
-
-FS::cust_pkg - Object methods for cust_pkg objects
-
-=head1 SYNOPSIS
-
- use FS::cust_pkg;
-
- $record = new FS::cust_pkg \%hash;
- $record = new FS::cust_pkg { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->cancel;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $part_pkg = $record->part_pkg;
-
- @labels = $record->labels;
-
- $seconds = $record->seconds_since($timestamp);
-
- $error = FS::cust_pkg::order( $custnum, \@pkgparts );
- $error = FS::cust_pkg::order( $custnum, \@pkgparts, \@remove_pkgnums ] );
-
-=head1 DESCRIPTION
-
-An FS::cust_pkg object represents a customer billing item. FS::cust_pkg
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item pkgnum - primary key (assigned automatically for new billing items)
-
-=item custnum - Customer (see L<FS::cust_main>)
-
-=item pkgpart - Billing item definition (see L<FS::part_pkg>)
-
-=item setup - date
-
-=item bill - date (next bill date)
-
-=item last_bill - last bill date
-
-=item adjourn - date
-
-=item susp - date
-
-=item expire - date
-
-=item cancel - date
-
-=item otaker - order taker (assigned automatically if null, see L<FS::UID>)
-
-=item manual_flag - If this field is set to 1, disables the automatic
-unsuspension of this package when using the B<unsuspendauto> config file.
-
-=back
-
-Note: setup, bill, adjourn, susp, expire and cancel are specified as UNIX timestamps;
-see L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for
-conversion functions.
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Create a new billing item. To add the item to the database, see L<"insert">.
-
-=cut
-
-sub table { 'cust_pkg'; }
-sub cust_linked { $_[0]->cust_main_custnum; }
-sub cust_unlinked_msg {
- my $self = shift;
- "WARNING: can't find cust_main.custnum ". $self->custnum.
- ' (cust_pkg.pkgnum '. $self->pkgnum. ')';
-}
-
-=item insert [ OPTION => VALUE ... ]
-
-Adds this billing item to the database ("Orders" the item). If there is an
-error, returns the error, otherwise returns false.
-
-If the additional field I<promo_code> is defined instead of I<pkgpart>, it
-will be used to look up the package definition and agent restrictions will be
-ignored.
-
-If the additional field I<refnum> is defined, an FS::pkg_referral record will
-be created and inserted. Multiple FS::pkg_referral records can be created by
-setting I<refnum> to an array reference of refnums or a hash reference with
-refnums as keys. If no I<refnum> is defined, a default FS::pkg_referral
-record will be created corresponding to cust_main.refnum.
-
-The following options are available: I<change>
-
-I<change>, if set true, supresses any referral credit to a referring customer.
-
-=cut
-
-sub insert {
- my( $self, %options ) = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::insert($options{options} ? %{$options{options}} : ());
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $self->refnum($self->cust_main->refnum) unless $self->refnum;
- $self->refnum( [ $self->refnum ] ) unless ref($self->refnum);
- $self->process_m2m( 'link_table' => 'pkg_referral',
- 'target_table' => 'part_referral',
- 'params' => $self->refnum,
- );
-
- #if ( $self->reg_code ) {
- # my $reg_code = qsearchs('reg_code', { 'code' => $self->reg_code } );
- # $error = $reg_code->delete;
- # if ( $error ) {
- # $dbh->rollback if $oldAutoCommit;
- # return $error;
- # }
- #}
-
- my $conf = new FS::Conf;
- my $cust_main = $self->cust_main;
- my $part_pkg = $self->part_pkg;
- if ( $conf->exists('referral_credit')
- && $cust_main->referral_custnum
- && ! $options{'change'}
- && $part_pkg->freq !~ /^0\D?$/
- )
- {
- my $referring_cust_main = $cust_main->referring_cust_main;
- if ( $referring_cust_main->status ne 'cancelled' ) {
- my $error;
- if ( $part_pkg->freq !~ /^\d+$/ ) {
- warn 'WARNING: Not crediting customer '. $cust_main->referral_custnum.
- ' for package '. $self->pkgnum.
- ' ( customer '. $self->custnum. ')'.
- ' - One-time referral credits not (yet) available for '.
- ' packages with '. $part_pkg->freq_pretty. ' frequency';
- } else {
-
- my $amount = sprintf( "%.2f", $part_pkg->base_recur / $part_pkg->freq );
- my $error =
- $referring_cust_main->
- credit( $amount,
- 'Referral credit for '.$cust_main->name,
- 'reason_type' => $conf->config('referral_credit_type')
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error crediting customer ". $cust_main->referral_custnum.
- " for referral: $error";
- }
-
- }
-
- }
- }
-
- if ($conf->config('welcome_letter') && $self->cust_main->num_pkgs == 1) {
- my $queue = new FS::queue {
- 'job' => 'FS::cust_main::queueable_print',
- };
- $error = $queue->insert(
- 'custnum' => $self->custnum,
- 'template' => 'welcome_letter',
- );
-
- if ($error) {
- warn "can't send welcome letter: $error";
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item delete
-
-This method now works but you probably shouldn't use it.
-
-You don't want to delete billing items, because there would then be no record
-the customer ever purchased the item. Instead, see the cancel method.
-
-=cut
-
-#sub delete {
-# return "Can't delete cust_pkg records!";
-#}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-Currently, custnum, setup, bill, adjourn, susp, expire, and cancel may be changed.
-
-Changing pkgpart may have disasterous effects. See the order subroutine.
-
-setup and bill are normally updated by calling the bill method of a customer
-object (see L<FS::cust_main>).
-
-suspend is normally updated by the suspend and unsuspend methods.
-
-cancel is normally updated by the cancel method (and also the order subroutine
-in some cases).
-
-Calls
-
-=cut
-
-sub replace {
- my( $new, $old, %options ) = @_;
-
- # We absolutely have to have an old vs. new record to make this work.
- if (!defined($old)) {
- $old = qsearchs( 'cust_pkg', { 'pkgnum' => $new->pkgnum } );
- }
- #return "Can't (yet?) change pkgpart!" if $old->pkgpart != $new->pkgpart;
- return "Can't change otaker!" if $old->otaker ne $new->otaker;
-
- #allow this *sigh*
- #return "Can't change setup once it exists!"
- # if $old->getfield('setup') &&
- # $old->getfield('setup') != $new->getfield('setup');
-
- #some logic for bill, susp, cancel?
-
- local($disable_agentcheck) = 1 if $old->pkgpart == $new->pkgpart;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $method ( qw(adjourn expire) ) { # How many reasons?
- if ($options{'reason'} && $new->$method && $old->$method ne $new->$method) {
- my $error = $new->insert_reason( 'reason' => $options{'reason'},
- 'date' => $new->$method,
- );
- if ( $error ) {
- dbh->rollback if $oldAutoCommit;
- return "Error inserting cust_pkg_reason: $error";
- }
- }
- }
-
- #save off and freeze RADIUS attributes for any associated svc_acct records
- my @svc_acct = ();
- if ( $old->part_pkg->is_prepaid || $new->part_pkg->is_prepaid ) {
-
- #also check for specific exports?
- # to avoid spurious modify export events
- @svc_acct = map { $_->svc_x }
- grep { $_->part_svc->svcdb eq 'svc_acct' }
- $old->cust_svc;
-
- $_->snapshot foreach @svc_acct;
-
- }
-
- my $error = $new->SUPER::replace($old,
- $options{options} ? ${options{options}} : ()
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- #for prepaid packages,
- #trigger export of new RADIUS Expiration attribute when cust_pkg.bill changes
- foreach my $old_svc_acct ( @svc_acct ) {
- my $new_svc_acct = new FS::svc_acct { $old_svc_acct->hash };
- my $s_error = $new_svc_acct->replace($old_svc_acct);
- if ( $s_error ) {
- $dbh->rollback if $oldAutoCommit;
- return $s_error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid billing item. If there is an
-error, returns the error, otherwise returns false. Called by the insert and
-replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('pkgnum')
- || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
- || $self->ut_numbern('pkgpart')
- || $self->ut_numbern('setup')
- || $self->ut_numbern('bill')
- || $self->ut_numbern('susp')
- || $self->ut_numbern('cancel')
- || $self->ut_numbern('adjourn')
- || $self->ut_numbern('expire')
- ;
- return $error if $error;
-
- if ( $self->reg_code ) {
-
- unless ( grep { $self->pkgpart == $_->pkgpart }
- map { $_->reg_code_pkg }
- qsearchs( 'reg_code', { 'code' => $self->reg_code,
- 'agentnum' => $self->cust_main->agentnum })
- ) {
- return "Unknown registration code";
- }
-
- } elsif ( $self->promo_code ) {
-
- my $promo_part_pkg =
- qsearchs('part_pkg', {
- 'pkgpart' => $self->pkgpart,
- 'promo_code' => { op=>'ILIKE', value=>$self->promo_code },
- } );
- return 'Unknown promotional code' unless $promo_part_pkg;
-
- } else {
-
- unless ( $disable_agentcheck ) {
- my $agent =
- qsearchs( 'agent', { 'agentnum' => $self->cust_main->agentnum } );
- my $pkgpart_href = $agent->pkgpart_hashref;
- return "agent ". $agent->agentnum.
- " can't purchase pkgpart ". $self->pkgpart
- unless $pkgpart_href->{ $self->pkgpart };
- }
-
- $error = $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart' );
- return $error if $error;
-
- }
-
- $self->otaker(getotaker) unless $self->otaker;
- $self->otaker =~ /^(\w{1,32})$/ or return "Illegal otaker";
- $self->otaker($1);
-
- if ( $self->dbdef_table->column('manual_flag') ) {
- $self->manual_flag('') if $self->manual_flag eq ' ';
- $self->manual_flag =~ /^([01]?)$/
- or return "Illegal manual_flag ". $self->manual_flag;
- $self->manual_flag($1);
- }
-
- $self->SUPER::check;
-}
-
-=item cancel [ OPTION => VALUE ... ]
-
-Cancels and removes all services (see L<FS::cust_svc> and L<FS::part_svc>)
-in this package, then cancels the package itself (sets the cancel field to
-now).
-
-Available options are:
-
-=over 4
-
-=item quiet - can be set true to supress email cancellation notices.
-
-=item time - can be set to cancel the package based on a specific future or historical date. Using time ensures that the remaining amount is calculated correctly. Note however that this is an immediate cancel and just changes the date. You are PROBABLY looking to expire the account instead of using this.
-
-=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
-
-=back
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub cancel {
- my( $self, %options ) = @_;
-
- warn "cust_pkg::cancel called with options".
- join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $cancel_time = $options{'time'} || time;
-
- my $error;
-
- if ( $options{'reason'} ) {
- $error = $self->insert_reason( 'reason' => $options{'reason'} );
- if ( $error ) {
- dbh->rollback if $oldAutoCommit;
- return "Error inserting cust_pkg_reason: $error";
- }
- }
-
- my %svc;
- foreach my $cust_svc (
- #schwartz
- map { $_->[0] }
- sort { $a->[1] <=> $b->[1] }
- map { [ $_, $_->svc_x->table_info->{'cancel_weight'} ]; }
- qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
- ) {
-
- my $error = $cust_svc->cancel;
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error cancelling cust_svc: $error";
- }
- }
-
- unless ( $self->getfield('cancel') ) {
- # Add a credit for remaining service
- my $remaining_value = $self->calc_remain(time=>$cancel_time);
- if ( $remaining_value > 0 && !$options{'no_credit'} ) {
- my $conf = new FS::Conf;
- my $error = $self->cust_main->credit(
- $remaining_value,
- 'Credit for unused time on '. $self->part_pkg->pkg,
- 'reason_type' => $conf->config('cancel_credit_type'),
- );
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return "Error crediting customer \$$remaining_value for unused time on".
- $self->part_pkg->pkg. ": $error";
- }
- }
- my %hash = $self->hash;
- $hash{'cancel'} = $cancel_time;
- my $new = new FS::cust_pkg ( \%hash );
- $error = $new->replace( $self, options => { $self->options } );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- my $conf = new FS::Conf;
- my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $self->cust_main->invoicing_list;
- if ( !$options{'quiet'} && $conf->exists('emailcancel') && @invoicing_list ) {
- my $conf = new FS::Conf;
- my $error = send_email(
- 'from' => $conf->config('invoice_from'),
- 'to' => \@invoicing_list,
- 'subject' => ( $conf->config('cancelsubject') || 'Cancellation Notice' ),
- 'body' => [ map "$_\n", $conf->config('cancelmessage') ],
- );
- #should this do something on errors?
- }
-
- ''; #no errors
-
-}
-
-=item cancel_if_expired [ NOW_TIMESTAMP ]
-
-Cancels this package if its expire date has been reached.
-
-=cut
-
-sub cancel_if_expired {
- my $self = shift;
- my $time = shift || time;
- return '' unless $self->expire && $self->expire <= $time;
- my $error = $self->cancel;
- if ( $error ) {
- return "Error cancelling expired pkg ". $self->pkgnum. " for custnum ".
- $self->custnum. ": $error";
- }
- '';
-}
-
-=item suspend [ OPTION => VALUE ... ]
-
-Suspends all services (see L<FS::cust_svc> and L<FS::part_svc>) in this
-package, then suspends the package itself (sets the susp field to now).
-
-Available options are:
-
-=over 4
-
-=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
-
-=back
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub suspend {
- my( $self, %options ) = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error;
-
- if ( $options{'reason'} ) {
- $error = $self->insert_reason( 'reason' => $options{'reason'} );
- if ( $error ) {
- dbh->rollback if $oldAutoCommit;
- return "Error inserting cust_pkg_reason: $error";
- }
- }
-
- foreach my $cust_svc (
- qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
- ) {
- my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $cust_svc->svcpart } );
-
- $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
- $dbh->rollback if $oldAutoCommit;
- return "Illegal svcdb value in part_svc!";
- };
- my $svcdb = $1;
- require "FS/$svcdb.pm";
-
- my $svc = qsearchs( $svcdb, { 'svcnum' => $cust_svc->svcnum } );
- if ($svc) {
- $error = $svc->suspend;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- }
-
- unless ( $self->getfield('susp') ) {
- my %hash = $self->hash;
- $hash{'susp'} = time;
- my $new = new FS::cust_pkg ( \%hash );
- $error = $new->replace( $self, options => { $self->options } );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- ''; #no errors
-}
-
-=item unsuspend [ OPTION => VALUE ... ]
-
-Unsuspends all services (see L<FS::cust_svc> and L<FS::part_svc>) in this
-package, then unsuspends the package itself (clears the susp field and the
-adjourn field if it is in the past).
-
-Available options are: I<adjust_next_bill>.
-
-I<adjust_next_bill> can be set true to adjust the next bill date forward by
-the amount of time the account was inactive. This was set true by default
-since 1.4.2 and 1.5.0pre6; however, starting with 1.7.0 this needs to be
-explicitly requested. Price plans for which this makes sense (anniversary-date
-based than prorate or subscription) could have an option to enable this
-behaviour?
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub unsuspend {
- my( $self, %opt ) = @_;
- my $error;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $cust_svc (
- qsearch('cust_svc',{'pkgnum'=> $self->pkgnum } )
- ) {
- my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $cust_svc->svcpart } );
-
- $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
- $dbh->rollback if $oldAutoCommit;
- return "Illegal svcdb value in part_svc!";
- };
- my $svcdb = $1;
- require "FS/$svcdb.pm";
-
- my $svc = qsearchs( $svcdb, { 'svcnum' => $cust_svc->svcnum } );
- if ($svc) {
- $error = $svc->unsuspend;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- }
-
- unless ( ! $self->getfield('susp') ) {
- my %hash = $self->hash;
- my $inactive = time - $hash{'susp'};
-
- my $conf = new FS::Conf;
-
- $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive
- if ( $opt{'adjust_next_bill'}
- || $conf->config('unsuspend-always_adjust_next_bill_date') )
- && $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} );
-
- $hash{'susp'} = '';
- $hash{'adjourn'} = '' if $hash{'adjourn'} < time;
- my $new = new FS::cust_pkg ( \%hash );
- $error = $new->replace( $self, options => { $self->options } );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- ''; #no errors
-}
-
-=item last_bill
-
-Returns the last bill date, or if there is no last bill date, the setup date.
-Useful for billing metered services.
-
-=cut
-
-sub last_bill {
- my $self = shift;
- if ( $self->dbdef_table->column('last_bill') ) {
- return $self->setfield('last_bill', $_[0]) if @_;
- return $self->getfield('last_bill') if $self->getfield('last_bill');
- }
- my $cust_bill_pkg = qsearchs('cust_bill_pkg', { 'pkgnum' => $self->pkgnum,
- 'edate' => $self->bill, } );
- $cust_bill_pkg ? $cust_bill_pkg->sdate : $self->setup || 0;
-}
-
-=item last_reason
-
-Returns the most recent FS::reason associated with the package.
-
-=cut
-
-sub last_reason {
- my $self = shift;
- my $cust_pkg_reason = qsearchs( {
- 'table' => 'cust_pkg_reason',
- 'hashref' => { 'pkgnum' => $self->pkgnum, },
- 'extra_sql'=> 'ORDER BY date DESC LIMIT 1',
- } );
- qsearchs ( 'reason', { 'reasonnum' => $cust_pkg_reason->reasonnum } )
- if $cust_pkg_reason;
-}
-
-=item part_pkg
-
-Returns the definition for this billing item, as an FS::part_pkg object (see
-L<FS::part_pkg>).
-
-=cut
-
-sub part_pkg {
- my $self = shift;
- #exists( $self->{'_pkgpart'} )
- $self->{'_pkgpart'}
- ? $self->{'_pkgpart'}
- : qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
-}
-
-=item old_cust_pkg
-
-Returns the cancelled package this package was changed from, if any.
-
-=cut
-
-sub old_cust_pkg {
- my $self = shift;
- return '' unless $self->change_pkgnum;
- qsearchs('cust_pkg', { 'pkgnum' => $self->change_pkgnum } );
-}
-
-=item calc_setup
-
-Calls the I<calc_setup> of the FS::part_pkg object associated with this billing
-item.
-
-=cut
-
-sub calc_setup {
- my $self = shift;
- $self->part_pkg->calc_setup($self, @_);
-}
-
-=item calc_recur
-
-Calls the I<calc_recur> of the FS::part_pkg object associated with this billing
-item.
-
-=cut
-
-sub calc_recur {
- my $self = shift;
- $self->part_pkg->calc_recur($self, @_);
-}
-
-=item calc_remain
-
-Calls the I<calc_remain> of the FS::part_pkg object associated with this
-billing item.
-
-=cut
-
-sub calc_remain {
- my $self = shift;
- $self->part_pkg->calc_remain($self, @_);
-}
-
-=item calc_cancel
-
-Calls the I<calc_cancel> of the FS::part_pkg object associated with this
-billing item.
-
-=cut
-
-sub calc_cancel {
- my $self = shift;
- $self->part_pkg->calc_cancel($self, @_);
-}
-
-=item cust_bill_pkg
-
-Returns any invoice line items for this package (see L<FS::cust_bill_pkg>).
-
-=cut
-
-sub cust_bill_pkg {
- my $self = shift;
- qsearch( 'cust_bill_pkg', { 'pkgnum' => $self->pkgnum } );
-}
-
-=item cust_event
-
-Returns the new-style customer billing events (see L<FS::cust_event>) for this invoice.
-
-=cut
-
-#false laziness w/cust_bill.pm
-sub cust_event {
- my $self = shift;
- qsearch({
- 'table' => 'cust_event',
- 'addl_from' => 'JOIN part_event USING ( eventpart )',
- 'hashref' => { 'tablenum' => $self->pkgnum },
- 'extra_sql' => " AND eventtable = 'cust_pkg' ",
- });
-}
-
-=item num_cust_event
-
-Returns the number of new-style customer billing events (see L<FS::cust_event>) for this invoice.
-
-=cut
-
-#false laziness w/cust_bill.pm
-sub num_cust_event {
- my $self = shift;
- my $sql =
- "SELECT COUNT(*) FROM cust_event JOIN part_event USING ( eventpart ) ".
- " WHERE tablenum = ? AND eventtable = 'cust_pkg'";
- my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
- $sth->execute($self->pkgnum) or die $sth->errstr. " executing $sql";
- $sth->fetchrow_arrayref->[0];
-}
-
-=item cust_svc [ SVCPART ]
-
-Returns the services for this package, as FS::cust_svc objects (see
-L<FS::cust_svc>). If a svcpart is specified, return only the matching
-services.
-
-=cut
-
-sub cust_svc {
- my $self = shift;
-
- if ( @_ ) {
- return qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum,
- 'svcpart' => shift, } );
- }
-
- #if ( $self->{'_svcnum'} ) {
- # values %{ $self->{'_svcnum'}->cache };
- #} else {
- $self->_sort_cust_svc(
- [ qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ]
- );
- #}
-
-}
-
-=item overlimit [ SVCPART ]
-
-Returns the services for this package which have exceeded their
-usage limit as FS::cust_svc objects (see L<FS::cust_svc>). If a svcpart
-is specified, return only the matching services.
-
-=cut
-
-sub overlimit {
- my $self = shift;
- grep { $_->overlimit } $self->cust_svc;
-}
-
-=item h_cust_svc END_TIMESTAMP [ START_TIMESTAMP ]
-
-Returns historical services for this package created before END TIMESTAMP and
-(optionally) not cancelled before START_TIMESTAMP, as FS::h_cust_svc objects
-(see L<FS::h_cust_svc>).
-
-=cut
-
-sub h_cust_svc {
- my $self = shift;
-
- $self->_sort_cust_svc(
- [ qsearch( 'h_cust_svc',
- { 'pkgnum' => $self->pkgnum, },
- FS::h_cust_svc->sql_h_search(@_),
- )
- ]
- );
-}
-
-sub _sort_cust_svc {
- my( $self, $arrayref ) = @_;
-
- map { $_->[0] }
- sort { $b->[1] cmp $a->[1] or $a->[2] <=> $b->[2] }
- map {
- my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $self->pkgpart,
- 'svcpart' => $_->svcpart } );
- [ $_,
- $pkg_svc ? $pkg_svc->primary_svc : '',
- $pkg_svc ? $pkg_svc->quantity : 0,
- ];
- }
- @$arrayref;
-
-}
-
-=item num_cust_svc [ SVCPART ]
-
-Returns the number of provisioned services for this package. If a svcpart is
-specified, counts only the matching services.
-
-=cut
-
-sub num_cust_svc {
- my $self = shift;
- my $sql = 'SELECT COUNT(*) FROM cust_svc WHERE pkgnum = ?';
- $sql .= ' AND svcpart = ?' if @_;
- my $sth = dbh->prepare($sql) or die dbh->errstr;
- $sth->execute($self->pkgnum, @_) or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
-
-=item available_part_svc
-
-Returns a list of FS::part_svc objects representing services included in this
-package but not yet provisioned. Each FS::part_svc object also has an extra
-field, I<num_avail>, which specifies the number of available services.
-
-=cut
-
-sub available_part_svc {
- my $self = shift;
- grep { $_->num_avail > 0 }
- map {
- my $part_svc = $_->part_svc;
- $part_svc->{'Hash'}{'num_avail'} = #evil encapsulation-breaking
- $_->quantity - $self->num_cust_svc($_->svcpart);
- $part_svc;
- }
- $self->part_pkg->pkg_svc;
-}
-
-=item part_svc
-
-Returns a list of FS::part_svc objects representing provisioned and available
-services included in this package. Each FS::part_svc object also has the
-following extra fields:
-
-=over 4
-
-=item num_cust_svc (count)
-
-=item num_avail (quantity - count)
-
-=item cust_pkg_svc (services) - array reference containing the provisioned services, as cust_svc objects
-
-svcnum
-label -> ($cust_svc->label)[1]
-
-=back
-
-=cut
-
-sub part_svc {
- my $self = shift;
-
- #XXX some sort of sort order besides numeric by svcpart...
- my @part_svc = sort { $a->svcpart <=> $b->svcpart } map {
- my $pkg_svc = $_;
- my $part_svc = $pkg_svc->part_svc;
- my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart);
- $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #more evil
- $part_svc->{'Hash'}{'num_avail'} =
- max( 0, $pkg_svc->quantity - $num_cust_svc );
- $part_svc->{'Hash'}{'cust_pkg_svc'} = [ $self->cust_svc($part_svc->svcpart) ];
- $part_svc;
- } $self->part_pkg->pkg_svc;
-
- #extras
- push @part_svc, map {
- my $part_svc = $_;
- my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart);
- $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #speak no evail
- $part_svc->{'Hash'}{'num_avail'} = 0; #0-$num_cust_svc ?
- $part_svc->{'Hash'}{'cust_pkg_svc'} = [ $self->cust_svc($part_svc->svcpart) ];
- $part_svc;
- } $self->extra_part_svc;
-
- @part_svc;
-
-}
-
-=item extra_part_svc
-
-Returns a list of FS::part_svc objects corresponding to services in this
-package which are still provisioned but not (any longer) available in the
-package definition.
-
-=cut
-
-sub extra_part_svc {
- my $self = shift;
-
- my $pkgnum = $self->pkgnum;
- my $pkgpart = $self->pkgpart;
-
- qsearch( {
- 'table' => 'part_svc',
- 'hashref' => {},
- 'extra_sql' => "WHERE 0 = ( SELECT COUNT(*) FROM pkg_svc
- WHERE pkg_svc.svcpart = part_svc.svcpart
- AND pkg_svc.pkgpart = $pkgpart
- AND quantity > 0
- )
- AND 0 < ( SELECT count(*)
- FROM cust_svc
- LEFT JOIN cust_pkg using ( pkgnum )
- WHERE cust_svc.svcpart = part_svc.svcpart
- AND pkgnum = $pkgnum
- )",
- } );
-}
-
-=item status
-
-Returns a short status string for this package, currently:
-
-=over 4
-
-=item not yet billed
-
-=item one-time charge
-
-=item active
-
-=item suspended
-
-=item cancelled
-
-=back
-
-=cut
-
-sub status {
- my $self = shift;
-
- my $freq = length($self->freq) ? $self->freq : $self->part_pkg->freq;
-
- return 'cancelled' if $self->get('cancel');
- return 'suspended' if $self->susp;
- return 'not yet billed' unless $self->setup;
- return 'one-time charge' if $freq =~ /^(0|$)/;
- return 'active';
-}
-
-=item statuses
-
-Class method that returns the list of possible status strings for packages
-(see L<the status method|/status>). For example:
-
- @statuses = FS::cust_pkg->statuses();
-
-=cut
-
-tie my %statuscolor, 'Tie::IxHash',
- 'not yet billed' => '000000',
- 'one-time charge' => '000000',
- 'active' => '00CC00',
- 'suspended' => 'FF9900',
- 'cancelled' => 'FF0000',
-;
-
-sub statuses {
- my $self = shift; #could be class...
- grep { $_ !~ /^(not yet billed)$/ } #this is a dumb status anyway
- # mayble split btw one-time vs. recur
- keys %statuscolor;
-}
-
-=item statuscolor
-
-Returns a hex triplet color string for this package's status.
-
-=cut
-
-sub statuscolor {
- my $self = shift;
- $statuscolor{$self->status};
-}
-
-=item labels
-
-Returns a list of lists, calling the label method for all services
-(see L<FS::cust_svc>) of this billing item.
-
-=cut
-
-sub labels {
- my $self = shift;
- map { [ $_->label ] } $self->cust_svc;
-}
-
-=item h_labels END_TIMESTAMP [ START_TIMESTAMP ]
-
-Like the labels method, but returns historical information on services that
-were active as of END_TIMESTAMP and (optionally) not cancelled before
-START_TIMESTAMP.
-
-Returns a list of lists, calling the label method for all (historical) services
-(see L<FS::h_cust_svc>) of this billing item.
-
-=cut
-
-sub h_labels {
- my $self = shift;
- map { [ $_->label(@_) ] } $self->h_cust_svc(@_);
-}
-
-=item h_labels_short END_TIMESTAMP [ START_TIMESTAMP ]
-
-Like h_labels, except returns a simple flat list, and shortens long
-(currently >5) lists of identical services to one line that lists the service
-label and the number of individual services rather than individual items.
-
-=cut
-
-sub h_labels_short {
- my $self = shift;
-
- my %labels;
- #tie %labels, 'Tie::IxHash';
- push @{ $labels{$_->[0]} }, $_->[1]
- foreach $self->h_labels(@_);
- my @labels;
- foreach my $label ( keys %labels ) {
- my @values = @{ $labels{$label} };
- my $num = scalar(@values);
- if ( $num > 5 ) {
- push @labels, "$label ($num)";
- } else {
- push @labels, map { "$label: $_" } @values;
- }
- }
-
- @labels;
-
-}
-
-=item cust_main
-
-Returns the parent customer object (see L<FS::cust_main>).
-
-=cut
-
-sub cust_main {
- my $self = shift;
- qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-}
-
-=item seconds_since TIMESTAMP
-
-Returns the number of seconds all accounts (see L<FS::svc_acct>) in this
-package have been online since TIMESTAMP, according to the session monitor.
-
-TIMESTAMP is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-sub seconds_since {
- my($self, $since) = @_;
- my $seconds = 0;
-
- foreach my $cust_svc (
- grep { $_->part_svc->svcdb eq 'svc_acct' } $self->cust_svc
- ) {
- $seconds += $cust_svc->seconds_since($since);
- }
-
- $seconds;
-
-}
-
-=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
-
-Returns the numbers of seconds all accounts (see L<FS::svc_acct>) in this
-package have been online between TIMESTAMP_START (inclusive) and TIMESTAMP_END
-(exclusive).
-
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-
-=cut
-
-sub seconds_since_sqlradacct {
- my($self, $start, $end) = @_;
-
- my $seconds = 0;
-
- foreach my $cust_svc (
- grep {
- my $part_svc = $_->part_svc;
- $part_svc->svcdb eq 'svc_acct'
- && scalar($part_svc->part_export('sqlradius'));
- } $self->cust_svc
- ) {
- $seconds += $cust_svc->seconds_since_sqlradacct($start, $end);
- }
-
- $seconds;
-
-}
-
-=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
-
-Returns the sum of the given attribute for all accounts (see L<FS::svc_acct>)
-in this package for sessions ending between TIMESTAMP_START (inclusive) and
-TIMESTAMP_END
-(exclusive).
-
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-=cut
-
-sub attribute_since_sqlradacct {
- my($self, $start, $end, $attrib) = @_;
-
- my $sum = 0;
-
- foreach my $cust_svc (
- grep {
- my $part_svc = $_->part_svc;
- $part_svc->svcdb eq 'svc_acct'
- && scalar($part_svc->part_export('sqlradius'));
- } $self->cust_svc
- ) {
- $sum += $cust_svc->attribute_since_sqlradacct($start, $end, $attrib);
- }
-
- $sum;
-
-}
-
-=item transfer DEST_PKGNUM | DEST_CUST_PKG, [ OPTION => VALUE ... ]
-
-Transfers as many services as possible from this package to another package.
-
-The destination package can be specified by pkgnum by passing an FS::cust_pkg
-object. The destination package must already exist.
-
-Services are moved only if the destination allows services with the correct
-I<svcpart> (not svcdb), unless the B<change_svcpart> option is set true. Use
-this option with caution! No provision is made for export differences
-between the old and new service definitions. Probably only should be used
-when your exports for all service definitions of a given svcdb are identical.
-(attempt a transfer without it first, to move all possible svcpart-matching
-services)
-
-Any services that can't be moved remain in the original package.
-
-Returns an error, if there is one; otherwise, returns the number of services
-that couldn't be moved.
-
-=cut
-
-sub transfer {
- my ($self, $dest_pkgnum, %opt) = @_;
-
- my $remaining = 0;
- my $dest;
- my %target;
-
- if (ref ($dest_pkgnum) eq 'FS::cust_pkg') {
- $dest = $dest_pkgnum;
- $dest_pkgnum = $dest->pkgnum;
- } else {
- $dest = qsearchs('cust_pkg', { pkgnum => $dest_pkgnum });
- }
-
- return ('Package does not exist: '.$dest_pkgnum) unless $dest;
-
- foreach my $pkg_svc ( $dest->part_pkg->pkg_svc ) {
- $target{$pkg_svc->svcpart} = $pkg_svc->quantity;
- }
-
- foreach my $cust_svc ($dest->cust_svc) {
- $target{$cust_svc->svcpart}--;
- }
-
- my %svcpart2svcparts = ();
- if ( exists $opt{'change_svcpart'} && $opt{'change_svcpart'} ) {
- warn "change_svcpart option received, creating alternates list\n" if $DEBUG;
- foreach my $svcpart ( map { $_->svcpart } $self->cust_svc ) {
- next if exists $svcpart2svcparts{$svcpart};
- my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
- $svcpart2svcparts{$svcpart} = [
- map { $_->[0] }
- sort { $b->[1] cmp $a->[1] or $a->[2] <=> $b->[2] }
- map {
- my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $dest->pkgpart,
- 'svcpart' => $_ } );
- [ $_,
- $pkg_svc ? $pkg_svc->primary_svc : '',
- $pkg_svc ? $pkg_svc->quantity : 0,
- ];
- }
-
- grep { $_ != $svcpart }
- map { $_->svcpart }
- qsearch('part_svc', { 'svcdb' => $part_svc->svcdb } )
- ];
- warn "alternates for svcpart $svcpart: ".
- join(', ', @{$svcpart2svcparts{$svcpart}}). "\n"
- if $DEBUG;
- }
- }
-
- foreach my $cust_svc ($self->cust_svc) {
- if($target{$cust_svc->svcpart} > 0) {
- $target{$cust_svc->svcpart}--;
- my $new = new FS::cust_svc { $cust_svc->hash };
- $new->pkgnum($dest_pkgnum);
- my $error = $new->replace($cust_svc);
- return $error if $error;
- } elsif ( exists $opt{'change_svcpart'} && $opt{'change_svcpart'} ) {
- if ( $DEBUG ) {
- warn "looking for alternates for svcpart ". $cust_svc->svcpart. "\n";
- warn "alternates to consider: ".
- join(', ', @{$svcpart2svcparts{$cust_svc->svcpart}}). "\n";
- }
- my @alternate = grep {
- warn "considering alternate svcpart $_: ".
- "$target{$_} available in new package\n"
- if $DEBUG;
- $target{$_} > 0;
- } @{$svcpart2svcparts{$cust_svc->svcpart}};
- if ( @alternate ) {
- warn "alternate(s) found\n" if $DEBUG;
- my $change_svcpart = $alternate[0];
- $target{$change_svcpart}--;
- my $new = new FS::cust_svc { $cust_svc->hash };
- $new->svcpart($change_svcpart);
- $new->pkgnum($dest_pkgnum);
- my $error = $new->replace($cust_svc);
- return $error if $error;
- } else {
- $remaining++;
- }
- } else {
- $remaining++
- }
- }
- return $remaining;
-}
-
-=item reexport
-
-This method is deprecated. See the I<depend_jobnum> option to the insert and
-order_pkgs methods in FS::cust_main for a better way to defer provisioning.
-
-=cut
-
-sub reexport {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $cust_svc ( $self->cust_svc ) {
- #false laziness w/svc_Common::insert
- my $svc_x = $cust_svc->svc_x;
- foreach my $part_export ( $cust_svc->part_svc->part_export ) {
- my $error = $part_export->export_insert($svc_x);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item recurring_sql
-
-Returns an SQL expression identifying recurring packages.
-
-=cut
-
-sub recurring_sql { "
- '0' != ( select freq from part_pkg
- where cust_pkg.pkgpart = part_pkg.pkgpart )
-"; }
-
-=item onetime_sql
-
-Returns an SQL expression identifying one-time packages.
-
-=cut
-
-sub onetime_sql { "
- '0' = ( select freq from part_pkg
- where cust_pkg.pkgpart = part_pkg.pkgpart )
-"; }
-
-=item active_sql
-
-Returns an SQL expression identifying active packages.
-
-=cut
-
-sub active_sql { "
- ". $_[0]->recurring_sql(). "
- AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
- AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 )
-"; }
-
-=item inactive_sql
-
-Returns an SQL expression identifying inactive packages (one-time packages
-that are otherwise unsuspended/uncancelled).
-
-=cut
-
-sub inactive_sql { "
- ". $_[0]->onetime_sql(). "
- AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
- AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 )
-"; }
-
-=item susp_sql
-=item suspended_sql
-
-Returns an SQL expression identifying suspended packages.
-
-=cut
-
-sub suspended_sql { susp_sql(@_); }
-sub susp_sql {
- #$_[0]->recurring_sql(). ' AND '.
- "
- ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
- AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0
- ";
-}
-
-=item cancel_sql
-=item cancelled_sql
-
-Returns an SQL exprression identifying cancelled packages.
-
-=cut
-
-sub cancelled_sql { cancel_sql(@_); }
-sub cancel_sql {
- #$_[0]->recurring_sql(). ' AND '.
- "cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0";
-}
-
-=item search_sql HREF
-
-Returns a qsearch hash expression to search for parameters specified in HREF.
-Valid parameters are
-
-=over 4
-=item agentnum
-=item magic - /^(active|inactive|suspended|cancell?ed)$/
-=item status - /^(active|inactive|suspended|one-time charge|inactive|cancell?ed)$/
-=item classnum
-=item pkgpart - list specified how?
-=item setup - arrayref of beginning and ending epoch date
-=item last_bill - arrayref of beginning and ending epoch date
-=item bill - arrayref of beginning and ending epoch date
-=item adjourn - arrayref of beginning and ending epoch date
-=item susp - arrayref of beginning and ending epoch date
-=item expire - arrayref of beginning and ending epoch date
-=item cancel - arrayref of beginning and ending epoch date
-=item query - /^(pkgnum/APKG_pkgnum)$/
-=item cust_fields - a value suited to passing to FS::UI::Web::cust_header
-=item CurrentUser - specifies the user for agent virtualization
-=back
-
-=cut
-
-sub search_sql {
- my ($class, $params) = @_;
- my @where = ();
-
- ##
- # parse agent
- ##
-
- if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) {
- push @where,
- "cust_main.agentnum = $1";
- }
-
- ##
- # parse status
- ##
-
- if ( $params->{'magic'} eq 'active'
- || $params->{'status'} eq 'active' ) {
-
- push @where, FS::cust_pkg->active_sql();
-
- } elsif ( $params->{'magic'} eq 'inactive'
- || $params->{'status'} eq 'inactive' ) {
-
- push @where, FS::cust_pkg->inactive_sql();
-
- } elsif ( $params->{'magic'} eq 'suspended'
- || $params->{'status'} eq 'suspended' ) {
-
- push @where, FS::cust_pkg->suspended_sql();
-
- } elsif ( $params->{'magic'} =~ /^cancell?ed$/
- || $params->{'status'} =~ /^cancell?ed$/ ) {
-
- push @where, FS::cust_pkg->cancelled_sql();
-
- } elsif ( $params->{'status'} =~ /^(one-time charge|inactive)$/ ) {
-
- push @where, FS::cust_pkg->inactive_sql();
-
- }
-
- ###
- # parse package class
- ###
-
- #false lazinessish w/graph/cust_bill_pkg.cgi
- my $classnum = 0;
- my @pkg_class = ();
- if ( exists($params->{'classnum'})
- && $params->{'classnum'} =~ /^(\d*)$/
- )
- {
- $classnum = $1;
- if ( $classnum ) { #a specific class
- push @where, "classnum = $classnum";
-
- #@pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) );
- #die "classnum $classnum not found!" unless $pkg_class[0];
- #$title .= $pkg_class[0]->classname.' ';
-
- } elsif ( $classnum eq '' ) { #the empty class
-
- push @where, "classnum IS NULL";
- #$title .= 'Empty class ';
- #@pkg_class = ( '(empty class)' );
- } elsif ( $classnum eq '0' ) {
- #@pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } );
- #push @pkg_class, '(empty class)';
- } else {
- die "illegal classnum";
- }
- }
- #eslaf
-
- ###
- # parse part_pkg
- ###
-
- my $pkgpart = join (' OR pkgpart=',
- grep {$_} map { /^(\d+)$/; } ($params->{'pkgpart'}));
- push @where, '(pkgpart=' . $pkgpart . ')' if $pkgpart;
-
- ###
- # parse dates
- ###
-
- my $orderby = '';
-
- #false laziness w/report_cust_pkg.html
- my %disable = (
- 'all' => {},
- 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
- 'active' => { 'susp'=>1, 'cancel'=>1 },
- 'suspended' => { 'cancel' => 1 },
- 'cancelled' => {},
- '' => {},
- );
-
- foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
-
- next unless exists($params->{$field});
-
- my($beginning, $ending) = @{$params->{$field}};
-
- next if $beginning == 0 && $ending == 4294967295;
-
- push @where,
- "cust_pkg.$field IS NOT NULL",
- "cust_pkg.$field >= $beginning",
- "cust_pkg.$field <= $ending";
-
- $orderby ||= "ORDER BY cust_pkg.$field";
-
- }
-
- $orderby ||= 'ORDER BY bill';
-
- ###
- # parse magic, legacy, etc.
- ###
-
- if ( $params->{'magic'} &&
- $params->{'magic'} =~ /^(active|inactive|suspended|cancell?ed)$/
- ) {
-
- $orderby = 'ORDER BY pkgnum';
-
- if ( $params->{'pkgpart'} =~ /^(\d+)$/ ) {
- push @where, "pkgpart = $1";
- }
-
- } elsif ( $params->{'query'} eq 'pkgnum' ) {
-
- $orderby = 'ORDER BY pkgnum';
-
- } elsif ( $params->{'query'} eq 'APKG_pkgnum' ) {
-
- $orderby = 'ORDER BY pkgnum';
-
- push @where, '0 < (
- SELECT count(*) FROM pkg_svc
- WHERE pkg_svc.pkgpart = cust_pkg.pkgpart
- AND pkg_svc.quantity > ( SELECT count(*) FROM cust_svc
- WHERE cust_svc.pkgnum = cust_pkg.pkgnum
- AND cust_svc.svcpart = pkg_svc.svcpart
- )
- )';
-
- }
-
- ##
- # setup queries, links, subs, etc. for the search
- ##
-
- # here is the agent virtualization
- if ($params->{CurrentUser}) {
- my $access_user =
- qsearchs('access_user', { username => $params->{CurrentUser} });
-
- if ($access_user) {
- push @where, $access_user->agentnums_sql('table'=>'cust_main');
- }else{
- push @where, "1=0";
- }
- }else{
- push @where, $FS::CurrentUser::CurrentUser->agentnums_sql('table'=>'cust_main');
- }
-
- my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
-
- my $addl_from = 'LEFT JOIN cust_main USING ( custnum ) '.
- 'LEFT JOIN part_pkg USING ( pkgpart ) '.
- 'LEFT JOIN pkg_class USING ( classnum ) ';
-
- my $count_query = "SELECT COUNT(*) FROM cust_pkg $addl_from $extra_sql";
-
- my $sql_query = {
- 'table' => 'cust_pkg',
- 'hashref' => {},
- 'select' => join(', ',
- 'cust_pkg.*',
- ( map "part_pkg.$_", qw( pkg freq ) ),
- 'pkg_class.classname',
- 'cust_main.custnum as cust_main_custnum',
- FS::UI::Web::cust_sql_fields(
- $params->{'cust_fields'}
- ),
- ),
- 'extra_sql' => "$extra_sql $orderby",
- 'addl_from' => $addl_from,
- 'count_query' => $count_query,
- };
-
-}
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item order CUSTNUM, PKGPARTS_ARYREF, [ REMOVE_PKGNUMS_ARYREF [ RETURN_CUST_PKG_ARRAYREF [ REFNUM ] ] ]
-
-CUSTNUM is a customer (see L<FS::cust_main>)
-
-PKGPARTS is a list of pkgparts specifying the the billing item definitions (see
-L<FS::part_pkg>) to order for this customer. Duplicates are of course
-permitted.
-
-REMOVE_PKGNUMS is an optional list of pkgnums specifying the billing items to
-remove for this customer. The services (see L<FS::cust_svc>) are moved to the
-new billing items. An error is returned if this is not possible (see
-L<FS::pkg_svc>). An empty arrayref is equivalent to not specifying this
-parameter.
-
-RETURN_CUST_PKG_ARRAYREF, if specified, will be filled in with the
-newly-created cust_pkg objects.
-
-REFNUM, if specified, will specify the FS::pkg_referral record to be created
-and inserted. Multiple FS::pkg_referral records can be created by
-setting I<refnum> to an array reference of refnums or a hash reference with
-refnums as keys. If no I<refnum> is defined, a default FS::pkg_referral
-record will be created corresponding to cust_main.refnum.
-
-=cut
-
-sub order {
- my ($custnum, $pkgparts, $remove_pkgnum, $return_cust_pkg, $refnum) = @_;
-
- my $conf = new FS::Conf;
-
- # Transactionize this whole mess
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error;
- my $cust_main = qsearchs('cust_main', { custnum => $custnum });
- return "Customer not found: $custnum" unless $cust_main;
-
- my @old_cust_pkg = map { qsearchs('cust_pkg', { pkgnum => $_ }) }
- @$remove_pkgnum;
-
- my $change = scalar(@old_cust_pkg) != 0;
-
- my %hash = ();
- if ( scalar(@old_cust_pkg) == 1 && scalar(@$pkgparts) == 1 ) {
-
- my $time = time;
-
- #$hash{$_} = $old_cust_pkg[0]->$_() foreach qw( last_bill bill );
-
- #$hash{$_} = $old_cust_pkg[0]->$_() foreach qw( setup );
- $hash{'setup'} = $time if $old_cust_pkg[0]->setup;
-
- $hash{'change_date'} = $time;
- $hash{"change_$_"} = $old_cust_pkg[0]->$_() foreach qw( pkgnum pkgpart );
- }
-
- # Create the new packages.
- foreach my $pkgpart (@$pkgparts) {
- my $cust_pkg = new FS::cust_pkg { custnum => $custnum,
- pkgpart => $pkgpart,
- refnum => $refnum,
- %hash,
- };
- $error = $cust_pkg->insert( 'change' => $change );
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- push @$return_cust_pkg, $cust_pkg;
- }
- # $return_cust_pkg now contains refs to all of the newly
- # created packages.
-
- # Transfer services and cancel old packages.
- foreach my $old_pkg (@old_cust_pkg) {
-
- foreach my $new_pkg (@$return_cust_pkg) {
- $error = $old_pkg->transfer($new_pkg);
- if ($error and $error == 0) {
- # $old_pkg->transfer failed.
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- if ( $error > 0 && $conf->exists('cust_pkg-change_svcpart') ) {
- warn "trying transfer again with change_svcpart option\n" if $DEBUG;
- foreach my $new_pkg (@$return_cust_pkg) {
- $error = $old_pkg->transfer($new_pkg, 'change_svcpart'=>1 );
- if ($error and $error == 0) {
- # $old_pkg->transfer failed.
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- if ($error > 0) {
- # Transfers were successful, but we went through all of the
- # new packages and still had services left on the old package.
- # We can't cancel the package under the circumstances, so abort.
- $dbh->rollback if $oldAutoCommit;
- return "Unable to transfer all services from package ".$old_pkg->pkgnum;
- }
- $error = $old_pkg->cancel( quiet=>1 );
- if ($error) {
- $dbh->rollback;
- return $error;
- }
- }
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-=item insert_reason
-
-Associates this package with a (suspension or cancellation) reason (see
-L<FS::cust_pkg_reason>, possibly inserting a new reason on the fly (see
-L<FS::reason>).
-
-Available options are:
-
-=over 4
-
-=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
-
-=item date
-
-=back
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-=item bulk_change PKGPARTS_ARYREF, REMOVE_PKGNUMS_ARYREF [ RETURN_CUST_PKG_ARRAYREF ]
-
-PKGPARTS is a list of pkgparts specifying the the billing item definitions (see
-L<FS::part_pkg>) to order for this customer. Duplicates are of course
-permitted.
-
-REMOVE_PKGNUMS is an list of pkgnums specifying the billing items to
-replace. The services (see L<FS::cust_svc>) are moved to the
-new billing items. An error is returned if this is not possible (see
-L<FS::pkg_svc>).
-
-RETURN_CUST_PKG_ARRAYREF, if specified, will be filled in with the
-newly-created cust_pkg objects.
-
-=cut
-
-sub bulk_change {
- my ($pkgparts, $remove_pkgnum, $return_cust_pkg) = @_;
-
- # Transactionize this whole mess
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my @errors;
- my @old_cust_pkg = map { qsearchs('cust_pkg', { pkgnum => $_ }) }
- @$remove_pkgnum;
-
- while(scalar(@old_cust_pkg)) {
- my @return = ();
- my $custnum = $old_cust_pkg[0]->custnum;
- my (@remove) = map { $_->pkgnum }
- grep { $_->custnum == $custnum } @old_cust_pkg;
- @old_cust_pkg = grep { $_->custnum != $custnum } @old_cust_pkg;
-
- my $error = order $custnum, $pkgparts, \@remove, \@return;
-
- push @errors, $error
- if $error;
- push @$return_cust_pkg, @return;
- }
-
- if (scalar(@errors)) {
- $dbh->rollback if $oldAutoCommit;
- return join(' / ', @errors);
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-sub insert_reason {
- my ($self, %options) = @_;
-
- my $otaker = $FS::CurrentUser::CurrentUser->username;
-
- my $reasonnum;
- if ( $options{'reason'} =~ /^(\d+)$/ ) {
-
- $reasonnum = $1;
-
- } elsif ( ref($options{'reason'}) ) {
-
- return 'Enter a new reason (or select an existing one)'
- unless $options{'reason'}->{'reason'} !~ /^\s*$/;
-
- my $reason = new FS::reason({
- 'reason_type' => $options{'reason'}->{'typenum'},
- 'reason' => $options{'reason'}->{'reason'},
- });
- my $error = $reason->insert;
- return $error if $error;
-
- $reasonnum = $reason->reasonnum;
-
- } else {
- return "Unparsable reason: ". $options{'reason'};
- }
-
- my $cust_pkg_reason =
- new FS::cust_pkg_reason({ 'pkgnum' => $self->pkgnum,
- 'reasonnum' => $reasonnum,
- 'otaker' => $otaker,
- 'date' => $options{'date'}
- ? $options{'date'}
- : time,
- });
-
- $cust_pkg_reason->insert;
-}
-
-=item set_usage USAGE_VALUE_HASHREF
-
-USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts
-to which they should be set (see L<FS::svc_acct>). Currently seconds,
-upbytes, downbytes, and totalbytes are appropriate keys.
-
-All svc_accts which are part of this package have their values reset.
-
-=cut
-
-sub set_usage {
- my ($self, $valueref) = @_;
-
- foreach my $cust_svc ($self->cust_svc){
- my $svc_x = $cust_svc->svc_x;
- $svc_x->set_usage($valueref)
- if $svc_x->can("set_usage");
- }
-}
-
-=item recharge USAGE_VALUE_HASHREF
-
-USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts
-to which they should be set (see L<FS::svc_acct>). Currently seconds,
-upbytes, downbytes, and totalbytes are appropriate keys.
-
-All svc_accts which are part of this package have their values incremented.
-
-=cut
-
-sub recharge {
- my ($self, $valueref) = @_;
-
- foreach my $cust_svc ($self->cust_svc){
- my $svc_x = $cust_svc->svc_x;
- $svc_x->recharge($valueref)
- if $svc_x->can("recharge");
- }
-}
-
-=back
-
-=head1 BUGS
-
-sub order is not OO. Perhaps it should be moved to FS::cust_main and made so?
-
-In sub order, the @pkgparts array (passed by reference) is clobbered.
-
-Also in sub order, no money is adjusted. Once FS::part_pkg defines a standard
-method to pass dates to the recur_prog expression, it should do so.
-
-FS::svc_acct, FS::svc_domain, FS::svc_www, FS::svc_ip and FS::svc_forward are
-loaded via 'use' at compile time, rather than via 'require' in sub { setup,
-suspend, unsuspend, cancel } because they use %FS::UID::callback to load
-configuration values. Probably need a subroutine which decides what to do
-based on whether or not we've fetched the user yet, rather than a hash. See
-FS::UID and the TODO.
-
-Now that things are transactional should the check in the insert method be
-moved to check ?
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_main>, L<FS::part_pkg>, L<FS::cust_svc>,
-L<FS::pkg_svc>, schema.html from the base documentation
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pkg_option.pm b/FS/FS/cust_pkg_option.pm
deleted file mode 100644
index 43a1530..0000000
--- a/FS/FS/cust_pkg_option.pm
+++ /dev/null
@@ -1,115 +0,0 @@
-package FS::cust_pkg_option;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cust_pkg_option - Object methods for cust_pkg_option records
-
-=head1 SYNOPSIS
-
- use FS::cust_pkg_option;
-
- $record = new FS::cust_pkg_option \%hash;
- $record = new FS::cust_pkg_option { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_pkg_option object represents an option key an value for a
-customer package. FS::cust_pkg_option inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item optionnum - primary key
-
-=item pkgnum -
-
-=item optionname -
-
-=item optionvalue -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new option. To add the option to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'cust_pkg_option'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-=item check
-
-Checks all fields to make sure this is a valid option. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('optionnum')
- || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
- || $self->ut_text('optionname')
- || $self->ut_textn('optionvalue')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_pkg>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pkg_reason.pm b/FS/FS/cust_pkg_reason.pm
deleted file mode 100644
index 2f92740..0000000
--- a/FS/FS/cust_pkg_reason.pm
+++ /dev/null
@@ -1,122 +0,0 @@
-package FS::cust_pkg_reason;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cust_pkg_reason - Object methods for cust_pkg_reason records
-
-=head1 SYNOPSIS
-
- use FS::cust_pkg_reason;
-
- $record = new FS::cust_pkg_reason \%hash;
- $record = new FS::cust_pkg_reason { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_pkg_reason object represents a relationship between a cust_pkg
-and a reason, for example cancellation or suspension reasons.
-FS::cust_pkg_reason inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item num - primary key
-
-=item pkgnum -
-
-=item reasonnum -
-
-=item otaker -
-
-=item date -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new cust_pkg_reason. To add the example to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'cust_pkg_reason'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-=item check
-
-Checks all fields to make sure this is a valid cust_pkg_reason. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('num')
- || $self->ut_number('pkgnum')
- || $self->ut_number('reasonnum')
- || $self->ut_text('otaker')
- || $self->ut_numbern('date')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-Here be termites. Don't use on wooden computers.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm
deleted file mode 100644
index 4981795..0000000
--- a/FS/FS/cust_refund.pm
+++ /dev/null
@@ -1,353 +0,0 @@
-package FS::cust_refund;
-
-use strict;
-use vars qw( @ISA @encrypted_fields );
-use Business::CreditCard;
-use FS::Record qw( qsearch qsearchs dbh );
-use FS::UID qw(getotaker);
-use FS::cust_credit;
-use FS::cust_credit_refund;
-use FS::cust_pay_refund;
-use FS::cust_main;
-use FS::payinfo_Mixin;
-
-@ISA = qw( FS::Record FS::payinfo_Mixin );
-
-@encrypted_fields = ('payinfo');
-
-=head1 NAME
-
-FS::cust_refund - Object method for cust_refund objects
-
-=head1 SYNOPSIS
-
- use FS::cust_refund;
-
- $record = new FS::cust_refund \%hash;
- $record = new FS::cust_refund { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_refund represents a refund: the transfer of money to a customer;
-equivalent to a negative payment (see L<FS::cust_pay>). FS::cust_refund
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item refundnum - primary key (assigned automatically for new refunds)
-
-=item custnum - customer (see L<FS::cust_main>)
-
-=item refund - Amount of the refund
-
-=item reason - Reason for the refund
-
-=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
-
-=item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format)
-
-=item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
-
-=item paybatch - text field for tracking card processing
-
-=item otaker - order taker (assigned automatically, see L<FS::UID>)
-
-=item closed - books closed flag, empty or `Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new refund. To add the refund to the database, see L<"insert">.
-
-=cut
-
-sub table { 'cust_refund'; }
-
-=item insert
-
-Adds this refund to the database.
-
-For backwards-compatibility and convenience, if the additional field crednum is
-defined, an FS::cust_credit_refund record for the full amount of the refund
-will be created. Or (this time for convenience and consistancy), if the
-additional field paynum is defined, an FS::cust_pay_refund record for the full
-amount of the refund will be created. In both cases, custnum is optional.
-
-=cut
-
-sub insert {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ( $self->crednum ) {
- my $cust_credit = qsearchs('cust_credit', { 'crednum' => $self->crednum } )
- or do {
- $dbh->rollback if $oldAutoCommit;
- return "Unknown cust_credit.crednum: ". $self->crednum;
- };
- $self->custnum($cust_credit->custnum);
- } elsif ( $self->paynum ) {
- my $cust_pay = qsearchs('cust_pay', { 'paynum' => $self->paynum } )
- or do {
- $dbh->rollback if $oldAutoCommit;
- return "Unknown cust_pay.paynum: ". $self->paynum;
- };
- $self->custnum($cust_pay->custnum);
- }
-
- my $error = $self->check;
- return $error if $error;
-
- $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $self->crednum ) {
- my $cust_credit_refund = new FS::cust_credit_refund {
- 'crednum' => $self->crednum,
- 'refundnum' => $self->refundnum,
- 'amount' => $self->refund,
- '_date' => $self->_date,
- };
- $error = $cust_credit_refund->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- #$self->custnum($cust_credit_refund->cust_credit->custnum);
- } elsif ( $self->paynum ) {
- my $cust_pay_refund = new FS::cust_pay_refund {
- 'paynum' => $self->paynum,
- 'refundnum' => $self->refundnum,
- 'amount' => $self->refund,
- '_date' => $self->_date,
- };
- $error = $cust_pay_refund->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-Unless the closed flag is set, deletes this refund and all associated
-applications (see L<FS::cust_credit_refund> and L<FS::cust_pay_refund>).
-
-=cut
-
-sub delete {
- my $self = shift;
- return "Can't delete closed refund" if $self->closed =~ /^Y/i;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $cust_credit_refund ( $self->cust_credit_refund ) {
- my $error = $cust_credit_refund->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- foreach my $cust_pay_refund ( $self->cust_pay_refund ) {
- my $error = $cust_pay_refund->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $error = $self->SUPER::delete(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item replace OLD_RECORD
-
-Currently unimplemented (accounting reasons).
-
-=cut
-
-sub replace {
- my $self = shift;
- $self->SUPER::replace(@_);
-}
-
-=item check
-
-Checks all fields to make sure this is a valid refund. If there is an error,
-returns the error, otherwise returns false. Called by the insert method.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->otaker(getotaker) unless ($self->otaker);
-
- my $error =
- $self->ut_numbern('refundnum')
- || $self->ut_numbern('custnum')
- || $self->ut_money('refund')
- || $self->ut_alpha('otaker')
- || $self->ut_text('reason')
- || $self->ut_numbern('_date')
- || $self->ut_textn('paybatch')
- || $self->ut_enum('closed', [ '', 'Y' ])
- ;
- return $error if $error;
-
- return "refund must be > 0 " if $self->refund <= 0;
-
- $self->_date(time) unless $self->_date;
-
- return "unknown cust_main.custnum: ". $self->custnum
- unless $self->crednum
- || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
-
- $error = $self->payinfo_check;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item cust_credit_refund
-
-Returns all applications to credits (see L<FS::cust_credit_refund>) for this
-refund.
-
-=cut
-
-sub cust_credit_refund {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_credit_refund', { 'refundnum' => $self->refundnum } )
- ;
-}
-
-=item cust_pay_refund
-
-Returns all applications to payments (see L<FS::cust_pay_refund>) for this
-refund.
-
-=cut
-
-sub cust_pay_refund {
- my $self = shift;
- sort { $a->_date <=> $b->_date }
- qsearch( 'cust_pay_refund', { 'refundnum' => $self->refundnum } )
- ;
-}
-
-=item unapplied
-
-Returns the amount of this refund that is still unapplied; which is
-amount minus all credit applications (see L<FS::cust_credit_refund>) and
-payment applications (see L<FS::cust_pay_refund>).
-
-=cut
-
-sub unapplied {
- my $self = shift;
- my $amount = $self->refund;
- $amount -= $_->amount foreach ( $self->cust_credit_refund );
- $amount -= $_->amount foreach ( $self->cust_pay_refund );
- sprintf("%.2f", $amount );
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item unapplied_sql
-
-Returns an SQL fragment to retreive the unapplied amount.
-
-=cut
-
-sub unapplied_sql {
- #my $class = shift;
-
- "refund
- - COALESCE(
- ( SELECT SUM(amount) FROM cust_credit_refund
- WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
- ,0
- )
- - COALESCE(
- ( SELECT SUM(amount) FROM cust_pay_refund
- WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
- ,0
- )
- ";
-
-}
-
-=back
-
-=head1 BUGS
-
-Delete and replace methods.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_credit>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm
deleted file mode 100644
index f168567..0000000
--- a/FS/FS/cust_svc.pm
+++ /dev/null
@@ -1,709 +0,0 @@
-package FS::cust_svc;
-
-use strict;
-use vars qw( @ISA $DEBUG $me $ignore_quantity );
-use Carp;
-use FS::Conf;
-use FS::Record qw( qsearch qsearchs dbh str2time_sql );
-use FS::cust_pkg;
-use FS::part_pkg;
-use FS::part_svc;
-use FS::pkg_svc;
-use FS::domain_record;
-use FS::part_export;
-use FS::cdr;
-
-#most FS::svc_ classes are autoloaded in svc_x emthod
-use FS::svc_acct; #this one is used in the cache stuff
-
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
-$DEBUG = 0;
-$me = '[cust_svc]';
-
-$ignore_quantity = 0;
-
-sub _cache {
- my $self = shift;
- my ( $hashref, $cache ) = @_;
- if ( $hashref->{'username'} ) {
- $self->{'_svc_acct'} = FS::svc_acct->new($hashref, '');
- }
- if ( $hashref->{'svc'} ) {
- $self->{'_svcpart'} = FS::part_svc->new($hashref);
- }
-}
-
-=head1 NAME
-
-FS::cust_svc - Object method for cust_svc objects
-
-=head1 SYNOPSIS
-
- use FS::cust_svc;
-
- $record = new FS::cust_svc \%hash
- $record = new FS::cust_svc { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- ($label, $value) = $record->label;
-
-=head1 DESCRIPTION
-
-An FS::cust_svc represents a service. FS::cust_svc inherits from FS::Record.
-The following fields are currently supported:
-
-=over 4
-
-=item svcnum - primary key (assigned automatically for new services)
-
-=item pkgnum - Package (see L<FS::cust_pkg>)
-
-=item svcpart - Service definition (see L<FS::part_svc>)
-
-=item overlimit - date the service exceeded its usage limit
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new service. To add the refund to the database, see L<"insert">.
-Services are normally created by creating FS::svc_ objects (see
-L<FS::svc_acct>, L<FS::svc_domain>, and L<FS::svc_forward>, among others).
-
-=cut
-
-sub table { 'cust_svc'; }
-
-=item insert
-
-Adds this service to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this service from the database. If there is an error, returns the
-error, otherwise returns false. Note that this only removes the cust_svc
-record - you should probably use the B<cancel> method instead.
-
-=item cancel
-
-Cancels the relevant service by calling the B<cancel> method of the associated
-FS::svc_XXX object (i.e. an FS::svc_acct object or FS::svc_domain object),
-deleting the FS::svc_XXX record and then deleting this record.
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub cancel {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $part_svc = $self->part_svc;
-
- $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
- $dbh->rollback if $oldAutoCommit;
- return "Illegal svcdb value in part_svc!";
- };
- my $svcdb = $1;
- require "FS/$svcdb.pm";
-
- my $svc = $self->svc_x;
- if ($svc) {
-
- my $error = $svc->cancel;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error canceling service: $error";
- }
- $error = $svc->delete; #this deletes this cust_svc record as well
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error deleting service: $error";
- }
-
- } else {
-
- #huh?
- warn "WARNING: no svc_ record found for svcnum ". $self->svcnum.
- "; deleting cust_svc only\n";
-
- my $error = $self->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error deleting cust_svc: $error";
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- ''; #no errors
-
-}
-
-=item overlimit [ ACTION ]
-
-Retrieves or sets the overlimit date. If ACTION is absent, return
-the present value of overlimit. If ACTION is present, it can
-have the value 'suspend' or 'unsuspend'. In the case of 'suspend' overlimit
-is set to the current time if it is not already set. The 'unsuspend' value
-causes the time to be cleared.
-
-If there is an error on setting, returns the error, otherwise returns false.
-
-=cut
-
-sub overlimit {
- my $self = shift;
- my $action = shift or return $self->getfield('overlimit');
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ( $action eq 'suspend' ) {
- $self->setfield('overlimit', time) unless $self->getfield('overlimit');
- }elsif ( $action eq 'unsuspend' ) {
- $self->setfield('overlimit', '');
- }else{
- die "unexpected action value: $action";
- }
-
- local $ignore_quantity = 1;
- my $error = $self->replace;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error setting overlimit: $error";
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- ''; #no errors
-
-}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my ( $new, $old ) = ( shift, shift );
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $old = $new->replace_old unless defined($old);
-
- if ( $new->svcpart != $old->svcpart ) {
- my $svc_x = $new->svc_x;
- my $new_svc_x = ref($svc_x)->new({$svc_x->hash, svcpart=>$new->svcpart });
- local($FS::Record::nowarn_identical) = 1;
- my $error = $new_svc_x->replace($svc_x);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error if $error;
- }
- }
-
- my $error = $new->SUPER::replace($old);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error if $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid service. If there is an error,
-returns the error, otherwise returns false. Called by the insert and
-replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('svcnum')
- || $self->ut_numbern('pkgnum')
- || $self->ut_number('svcpart')
- || $self->ut_numbern('overlimit')
- ;
- return $error if $error;
-
- my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
- return "Unknown svcpart" unless $part_svc;
-
- if ( $self->pkgnum ) {
- my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
- return "Unknown pkgnum" unless $cust_pkg;
- my $pkg_svc = qsearchs( 'pkg_svc', {
- 'pkgpart' => $cust_pkg->pkgpart,
- 'svcpart' => $self->svcpart,
- });
- # or new FS::pkg_svc ( { 'pkgpart' => $cust_pkg->pkgpart,
- # 'svcpart' => $self->svcpart,
- # 'quantity' => 0 } );
- my $quantity = $pkg_svc ? $pkg_svc->quantity : 0;
-
- my @cust_svc = qsearch('cust_svc', {
- 'pkgnum' => $self->pkgnum,
- 'svcpart' => $self->svcpart,
- });
- return "Already ". scalar(@cust_svc). " ". $part_svc->svc.
- " services for pkgnum ". $self->pkgnum
- if scalar(@cust_svc) >= $quantity && !$ignore_quantity;
- }
-
- $self->SUPER::check;
-}
-
-=item part_svc
-
-Returns the definition for this service, as a FS::part_svc object (see
-L<FS::part_svc>).
-
-=cut
-
-sub part_svc {
- my $self = shift;
- $self->{'_svcpart'}
- ? $self->{'_svcpart'}
- : qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
-}
-
-=item cust_pkg
-
-Returns the package this service belongs to, as a FS::cust_pkg object (see
-L<FS::cust_pkg>).
-
-=cut
-
-sub cust_pkg {
- my $self = shift;
- qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
-}
-
-=item pkg_svc
-
-Returns the pkg_svc record for for this service, if applicable.
-
-=cut
-
-sub pkg_svc {
- my $self = shift;
- my $cust_pkg = $self->cust_pkg;
- return undef unless $cust_pkg;
-
- qsearchs( 'pkg_svc', { 'svcpart' => $self->svcpart,
- 'pkgpart' => $cust_pkg->pkgpart,
- }
- );
-}
-
-=item date_inserted
-
-Returns the date this service was inserted.
-
-=cut
-
-sub date_inserted {
- my $self = shift;
- $self->h_date('insert');
-}
-
-=item label
-
-Returns a list consisting of:
-- The name of this service (from part_svc)
-- A meaningful identifier (username, domain, or mail alias)
-- The table name (i.e. svc_domain) for this service
-- svcnum
-
-Usage example:
-
- my($label, $value, $svcdb) = $cust_svc->label;
-
-=cut
-
-sub label {
- my $self = shift;
- carp "FS::cust_svc::label called on $self" if $DEBUG;
- my $svc_x = $self->svc_x
- or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum;
-
- $self->_svc_label($svc_x);
-}
-
-sub _svc_label {
- my( $self, $svc_x ) = ( shift, shift );
-
- (
- $self->part_svc->svc,
- $svc_x->label(@_),
- $self->part_svc->svcdb,
- $self->svcnum
- );
-
-}
-
-=item export_links
-
-Returns a list of html elements associated with this services exports.
-
-=cut
-
-sub export_links {
- my $self = shift;
- my $svc_x = $self->svc_x
- or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum;
-
- $svc_x->export_links;
-}
-
-=item svc_x
-
-Returns the FS::svc_XXX object for this service (i.e. an FS::svc_acct object or
-FS::svc_domain object, etc.)
-
-=cut
-
-sub svc_x {
- my $self = shift;
- my $svcdb = $self->part_svc->svcdb;
- if ( $svcdb eq 'svc_acct' && $self->{'_svc_acct'} ) {
- $self->{'_svc_acct'};
- } else {
- require "FS/$svcdb.pm";
- warn "$me svc_x: part_svc.svcpart ". $self->part_svc->svcpart.
- ", so searching for $svcdb.svcnum ". $self->svcnum. "\n"
- if $DEBUG;
- qsearchs( $svcdb, { 'svcnum' => $self->svcnum } );
- }
-}
-
-=item seconds_since TIMESTAMP
-
-See L<FS::svc_acct/seconds_since>. Equivalent to
-$cust_svc->svc_x->seconds_since, but more efficient. Meaningless for records
-where B<svcdb> is not "svc_acct".
-
-=cut
-
-#note: implementation here, POD in FS::svc_acct
-sub seconds_since {
- my($self, $since) = @_;
- my $dbh = dbh;
- my $sth = $dbh->prepare(' SELECT SUM(logout-login) FROM session
- WHERE svcnum = ?
- AND login >= ?
- AND logout IS NOT NULL'
- ) or die $dbh->errstr;
- $sth->execute($self->svcnum, $since) or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
-
-=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
-
-See L<FS::svc_acct/seconds_since_sqlradacct>. Equivalent to
-$cust_svc->svc_x->seconds_since_sqlradacct, but more efficient. Meaningless
-for records where B<svcdb> is not "svc_acct".
-
-=cut
-
-#note: implementation here, POD in FS::svc_acct
-sub seconds_since_sqlradacct {
- my($self, $start, $end) = @_;
-
- my $svc_x = $self->svc_x;
-
- my @part_export = $self->part_svc->part_export_usage;
- die "no accounting-capable exports are enabled for ". $self->part_svc->svc.
- " service definition"
- unless @part_export;
- #or return undef;
-
- my $seconds = 0;
- foreach my $part_export ( @part_export ) {
-
- next if $part_export->option('ignore_accounting');
-
- my $dbh = DBI->connect( map { $part_export->option($_) }
- qw(datasrc username password) )
- or die "can't connect to sqlradius database: ". $DBI::errstr;
-
- #select a unix time conversion function based on database type
- my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
-
- my $username = $part_export->export_username($svc_x);
-
- my $query;
-
- #find closed sessions completely within the given range
- my $sth = $dbh->prepare("SELECT SUM(acctsessiontime)
- FROM radacct
- WHERE UserName = ?
- AND $str2time AcctStartTime) >= ?
- AND $str2time AcctStopTime ) < ?
- AND $str2time AcctStopTime ) > 0
- AND AcctStopTime IS NOT NULL"
- ) or die $dbh->errstr;
- $sth->execute($username, $start, $end) or die $sth->errstr;
- my $regular = $sth->fetchrow_arrayref->[0];
-
- #find open sessions which start in the range, count session start->range end
- $query = "SELECT SUM( ? - $str2time AcctStartTime ) )
- FROM radacct
- WHERE UserName = ?
- AND $str2time AcctStartTime ) >= ?
- AND $str2time AcctStartTime ) < ?
- AND ( ? - $str2time AcctStartTime ) ) < 86400
- AND ( $str2time AcctStopTime ) = 0
- OR AcctStopTime IS NULL )";
- $sth = $dbh->prepare($query) or die $dbh->errstr;
- $sth->execute($end, $username, $start, $end, $end)
- or die $sth->errstr. " executing query $query";
- my $start_during = $sth->fetchrow_arrayref->[0];
-
- #find closed sessions which start before the range but stop during,
- #count range start->session end
- $sth = $dbh->prepare("SELECT SUM( $str2time AcctStopTime ) - ? )
- FROM radacct
- WHERE UserName = ?
- AND $str2time AcctStartTime ) < ?
- AND $str2time AcctStopTime ) >= ?
- AND $str2time AcctStopTime ) < ?
- AND $str2time AcctStopTime ) > 0
- AND AcctStopTime IS NOT NULL"
- ) or die $dbh->errstr;
- $sth->execute($start, $username, $start, $start, $end ) or die $sth->errstr;
- my $end_during = $sth->fetchrow_arrayref->[0];
-
- #find closed (not anymore - or open) sessions which start before the range
- # but stop after, or are still open, count range start->range end
- # don't count open sessions (probably missing stop record)
- $sth = $dbh->prepare("SELECT COUNT(*)
- FROM radacct
- WHERE UserName = ?
- AND $str2time AcctStartTime ) < ?
- AND ( $str2time AcctStopTime ) >= ?
- )"
- # OR AcctStopTime = 0
- # OR AcctStopTime IS NULL )"
- ) or die $dbh->errstr;
- $sth->execute($username, $start, $end ) or die $sth->errstr;
- my $entire_range = ($end-$start) * $sth->fetchrow_arrayref->[0];
-
- $seconds += $regular + $end_during + $start_during + $entire_range;
-
- }
-
- $seconds;
-
-}
-
-=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
-
-See L<FS::svc_acct/attribute_since_sqlradacct>. Equivalent to
-$cust_svc->svc_x->attribute_since_sqlradacct, but more efficient. Meaningless
-for records where B<svcdb> is not "svc_acct".
-
-=cut
-
-#note: implementation here, POD in FS::svc_acct
-#(false laziness w/seconds_since_sqlradacct above)
-sub attribute_since_sqlradacct {
- my($self, $start, $end, $attrib) = @_;
-
- my $svc_x = $self->svc_x;
-
- my @part_export = $self->part_svc->part_export_usage;
- die "no accounting-capable exports are enabled for ". $self->part_svc->svc.
- " service definition"
- unless @part_export;
- #or return undef;
-
- my $sum = 0;
-
- foreach my $part_export ( @part_export ) {
-
- next if $part_export->option('ignore_accounting');
-
- my $dbh = DBI->connect( map { $part_export->option($_) }
- qw(datasrc username password) )
- or die "can't connect to sqlradius database: ". $DBI::errstr;
-
- #select a unix time conversion function based on database type
- my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
-
- my $username = $part_export->export_username($svc_x);
-
- my $sth = $dbh->prepare("SELECT SUM($attrib)
- FROM radacct
- WHERE UserName = ?
- AND $str2time AcctStopTime ) >= ?
- AND $str2time AcctStopTime ) < ?
- AND AcctStopTime IS NOT NULL"
- ) or die $dbh->errstr;
- $sth->execute($username, $start, $end) or die $sth->errstr;
-
- $sum += $sth->fetchrow_arrayref->[0];
-
- }
-
- $sum;
-
-}
-
-=item get_session_history TIMESTAMP_START TIMESTAMP_END
-
-See L<FS::svc_acct/get_session_history>. Equivalent to
-$cust_svc->svc_x->get_session_history, but more efficient. Meaningless for
-records where B<svcdb> is not "svc_acct".
-
-=cut
-
-sub get_session_history {
- my($self, $start, $end, $attrib) = @_;
-
- #$attrib ???
-
- my @part_export = $self->part_svc->part_export_usage;
- die "no accounting-capable exports are enabled for ". $self->part_svc->svc.
- " service definition"
- unless @part_export;
- #or return undef;
-
- my @sessions = ();
-
- foreach my $part_export ( @part_export ) {
- push @sessions,
- @{ $part_export->usage_sessions( $start, $end, $self->svc_x ) };
- }
-
- @sessions;
-
-}
-
-=item get_cdrs_for_update
-
-Returns (and SELECTs "FOR UPDATE") all unprocessed (freesidestatus NULL) CDR
-objects (see L<FS::cdr>) associated with this service.
-
-CDRs are associated with svc_phone services via svc_phone.phonenum
-
-=cut
-
-sub get_cdrs_for_update {
- my($self, %options) = @_;
-
- my @cdrs = $self->get_cdrs_fromfield('charged_party', %options);
-
- push @cdrs, $self->get_cdrs_fromfield('src', %options)
- unless $options{'disable_src'};
-
- @cdrs;
-}
-
-sub get_cdrs_fromfield {
- my($self, $field, %options) = @_;
-
- my $default_prefix = $options{'default_prefix'};
-
- #CDRs are now associated with svc_phone services via svc_phone.phonenum
- #return () unless $self->svc_x->isa('FS::svc_phone');
- return () unless $self->part_svc->svcdb eq 'svc_phone';
- my $number = $self->svc_x->phonenum;
-
- my @cdrs =
- qsearch( {
- 'table' => 'cdr',
- 'hashref' => { 'freesidestatus' => '',
- $field => $number
- },
- 'extra_sql' => 'FOR UPDATE',
- } );
-
- if ( length($default_prefix) ) {
- push @cdrs,
- qsearch( {
- 'table' => 'cdr',
- 'hashref' => { 'freesidestatus' => '',
- $field => "$default_prefix$number",
- },
- 'extra_sql' => 'FOR UPDATE',
- } );
- }
-
- @cdrs;
-}
-
-=back
-
-=head1 BUGS
-
-Behaviour of changing the svcpart of cust_svc records is undefined and should
-possibly be prohibited, and pkg_svc records are not checked.
-
-pkg_svc records are not checked in general (here).
-
-Deleting this record doesn't check or delete the svc_* record associated
-with this record.
-
-In seconds_since_sqlradacct, specifying a DATASRC/USERNAME/PASSWORD instead of
-a DBI database handle is not yet implemented.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_pkg>, L<FS::part_svc>, L<FS::pkg_svc>,
-schema.html from the base documentation
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_tax_exempt.pm b/FS/FS/cust_tax_exempt.pm
deleted file mode 100644
index 3e39887..0000000
--- a/FS/FS/cust_tax_exempt.pm
+++ /dev/null
@@ -1,151 +0,0 @@
-package FS::cust_tax_exempt;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::cust_main;
-use FS::cust_main_county;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cust_tax_exempt - Object methods for cust_tax_exempt records
-
-=head1 SYNOPSIS
-
- use FS::cust_tax_exempt;
-
- $record = new FS::cust_tax_exempt \%hash;
- $record = new FS::cust_tax_exempt { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_tax_exempt object represents a record of an old-style customer tax
-exemption. Currently this is only used for "texas tax". FS::cust_tax_exempt
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item exemptnum - primary key
-
-=item custnum - customer (see L<FS::cust_main>)
-
-=item taxnum - tax rate (see L<FS::cust_main_county>)
-
-=item year
-
-=item month
-
-=item amount
-
-=back
-
-=head1 NOTE
-
-Old-style customer tax exemptions are only useful for legacy migrations - if
-you are looking for current customer tax exemption data see
-L<FS::cust_tax_exempt_pkg>.
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new exemption record. To add the example to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_tax_exempt'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid example. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('exemptnum')
- || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
- || $self->ut_foreign_key('taxnum', 'cust_main_county', 'taxnum')
- || $self->ut_number('year') #check better
- || $self->ut_number('month') #check better
- || $self->ut_money('amount')
- || $self->SUPER::check
- ;
-}
-
-=item cust_main_county
-
-Returns the FS::cust_main_county object associated with this tax exemption.
-
-=cut
-
-sub cust_main_county {
- my $self = shift;
- qsearchs( 'cust_main_county', { 'taxnum' => $self->taxnum } );
-}
-
-=back
-
-=head1 BUGS
-
-Texas tax is a royal pain in the ass.
-
-=head1 SEE ALSO
-
-L<FS::cust_main_county>, L<FS::cust_main>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_tax_exempt_pkg.pm b/FS/FS/cust_tax_exempt_pkg.pm
deleted file mode 100644
index 128921b..0000000
--- a/FS/FS/cust_tax_exempt_pkg.pm
+++ /dev/null
@@ -1,136 +0,0 @@
-package FS::cust_tax_exempt_pkg;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::cust_main_Mixin;
-use FS::cust_bill_pkg;
-use FS::cust_main_county;
-
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
-=head1 NAME
-
-FS::cust_tax_exempt_pkg - Object methods for cust_tax_exempt_pkg records
-
-=head1 SYNOPSIS
-
- use FS::cust_tax_exempt_pkg;
-
- $record = new FS::cust_tax_exempt_pkg \%hash;
- $record = new FS::cust_tax_exempt_pkg { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cust_tax_exempt_pkg object represents a record of a customer tax
-exemption. Currently this is only used for "texas tax". FS::cust_tax_exempt
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item exemptpkgnum - primary key
-
-=item billpkgnum - invoice line item (see L<FS::cust_bill_pkg>)
-
-=item taxnum - tax rate (see L<FS::cust_main_county>)
-
-=item year
-
-=item month
-
-=item amount
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new exemption record. To add the examption record to the database,
-see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cust_tax_exempt_pkg'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid exemption record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('exemptnum')
-# || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
- || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum')
- || $self->ut_foreign_key('taxnum', 'cust_main_county', 'taxnum')
- || $self->ut_number('year') #check better
- || $self->ut_number('month') #check better
- || $self->ut_money('amount')
- || $self->SUPER::check
- ;
-}
-
-=back
-
-=head1 BUGS
-
-Texas tax is still a royal pain in the ass.
-
-=head1 SEE ALSO
-
-L<FS::cust_main_county>, L<FS::cust_bill_pkg>, L<FS::Record>, schema.html from
-the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/domain_record.pm b/FS/FS/domain_record.pm
deleted file mode 100644
index 6513abf..0000000
--- a/FS/FS/domain_record.pm
+++ /dev/null
@@ -1,438 +0,0 @@
-package FS::domain_record;
-
-use strict;
-use vars qw( @ISA $noserial_hack $DEBUG );
-use FS::Conf;
-#use FS::Record qw( qsearch qsearchs );
-use FS::Record qw( qsearchs dbh );
-use FS::svc_domain;
-use FS::svc_www;
-
-@ISA = qw(FS::Record);
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::domain_record - Object methods for domain_record records
-
-=head1 SYNOPSIS
-
- use FS::domain_record;
-
- $record = new FS::domain_record \%hash;
- $record = new FS::domain_record { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::domain_record object represents an entry in a DNS zone.
-FS::domain_record inherits from FS::Record. The following fields are currently
-supported:
-
-=over 4
-
-=item recnum - primary key
-
-=item svcnum - Domain (see L<FS::svc_domain>) of this entry
-
-=item reczone - partial (or full) zone for this entry
-
-=item recaf - address family for this entry, currently only `IN' is recognized.
-
-=item rectype - record type for this entry (A, MX, etc.)
-
-=item recdata - data for this entry
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new entry. To add the entry to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'domain_record'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub insert {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ( $self->rectype eq '_mstr' ) { #delete all other records
- foreach my $domain_record ( reverse $self->svc_domain->domain_record ) {
- my $error = $domain_record->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- unless ( $self->rectype =~ /^(SOA|_mstr)$/ ) {
- my $error = $self->increment_serial;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $conf = new FS::Conf;
- if ( $self->rectype =~ /^A$/ && ! $conf->exists('disable_autoreverse') ) {
- my $reverse = $self->reverse_record;
- if ( $reverse && ! $reverse->recnum ) {
- my $error = $reverse->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error adding corresponding reverse-ARPA record: $error";
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- return "Can't delete a domain record which has a website!"
- if qsearchs( 'svc_www', { 'recnum' => $self->recnum } );
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- unless ( $self->rectype =~ /^(SOA|_mstr)$/ ) {
- my $error = $self->increment_serial;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $conf = new FS::Conf;
- if ( $self->rectype =~ /^A$/ && ! $conf->exists('disable_autoreverse') ) {
- my $reverse = $self->reverse_record;
- if ( $reverse && $reverse->recnum && $reverse->recdata eq $self->zone.'.' ){
- my $error = $reverse->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error removing corresponding reverse-ARPA record: $error";
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::replace(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- unless ( $self->rectype eq 'SOA' ) {
- my $error = $self->increment_serial;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid entry. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('recnum')
- || $self->ut_number('svcnum')
- ;
- return $error if $error;
-
- return "Unknown svcnum (in svc_domain)"
- unless qsearchs('svc_domain', { 'svcnum' => $self->svcnum } );
-
- my $conf = new FS::Conf;
-
- if ( $conf->exists('zone-underscore') ) {
- $self->reczone =~ /^(@|[a-z0-9_\.\-\*]+)$/i
- or return "Illegal reczone: ". $self->reczone;
- $self->reczone($1);
- } else {
- $self->reczone =~ /^(@|[a-z0-9\.\-\*]+)$/i
- or return "Illegal reczone: ". $self->reczone;
- $self->reczone($1);
- }
-
- $self->recaf =~ /^(IN)$/ or return "Illegal recaf: ". $self->recaf;
- $self->recaf($1);
-
- $self->rectype =~ /^(SOA|NS|MX|A|PTR|CNAME|TXT|_mstr)$/
- or return "Illegal rectype (only SOA NS MX A PTR CNAME TXT recognized): ".
- $self->rectype;
- $self->rectype($1);
-
- return "Illegal reczone for ". $self->rectype. ": ". $self->reczone
- if $self->rectype !~ /^MX$/i && $self->reczone =~ /\*/;
-
- if ( $self->rectype eq 'SOA' ) {
- my $recdata = $self->recdata;
- $recdata =~ s/\s+/ /g;
- $recdata =~ /^([a-z0-9\.\-]+ [\w\-\+]+\.[a-z0-9\.\-]+ \( ((\d+|((\d+[WDHMS])+)) ){5}\))$/i
- or return "Illegal data for SOA record: $recdata";
- $self->recdata($1);
- } elsif ( $self->rectype eq 'NS' ) {
- $self->recdata =~ /^([a-z0-9\.\-]+)$/i
- or return "Illegal data for NS record: ". $self->recdata;
- $self->recdata($1);
- } elsif ( $self->rectype eq 'MX' ) {
- $self->recdata =~ /^(\d+)\s+([a-z0-9\.\-]+)$/i
- or return "Illegal data for MX record: ". $self->recdata;
- $self->recdata("$1 $2");
- } elsif ( $self->rectype eq 'A' ) {
- $self->recdata =~ /^((\d{1,3}\.){3}\d{1,3})$/
- or return "Illegal data for A record: ". $self->recdata;
- $self->recdata($1);
- } elsif ( $self->rectype eq 'PTR' ) {
- if ( $conf->exists('zone-underscore') ) {
- $self->recdata =~ /^([a-z0-9_\.\-]+)$/i
- or return "Illegal data for PTR record: ". $self->recdata;
- $self->recdata($1);
- } else {
- $self->recdata =~ /^([a-z0-9\.\-]+)$/i
- or return "Illegal data for PTR record: ". $self->recdata;
- $self->recdata($1);
- }
- } elsif ( $self->rectype eq 'CNAME' ) {
- $self->recdata =~ /^([a-z0-9\.\-]+|\@)$/i
- or return "Illegal data for CNAME record: ". $self->recdata;
- $self->recdata($1);
- } elsif ( $self->rectype eq 'TXT' ) {
- if ( $self->recdata =~ /^((?:\S+)|(?:".+"))$/ ) {
- $self->recdata($1);
- } else {
- $self->recdata('"'. $self->recdata. '"'); #?
- }
- # or return "Illegal data for TXT record: ". $self->recdata;
- } elsif ( $self->rectype eq '_mstr' ) {
- $self->recdata =~ /^((\d{1,3}\.){3}\d{1,3})$/
- or return "Illegal data for _master pseudo-record: ". $self->recdata;
- } else {
- die "ack!";
- }
-
- $self->SUPER::check;
-}
-
-=item increment_serial
-
-=cut
-
-sub increment_serial {
- return '' if $noserial_hack;
- my $self = shift;
-
- my $soa = qsearchs('domain_record', {
- svcnum => $self->svcnum,
- reczone => '@',
- recaf => 'IN',
- rectype => 'SOA', } )
- || qsearchs('domain_record', {
- svcnum => $self->svcnum,
- reczone => $self->svc_domain->domain.'.',
- recaf => 'IN',
- rectype => 'SOA',
- } )
- or return "soa record not found; can't increment serial";
-
- my $data = $soa->recdata;
- $data =~ s/(\(\D*)(\d+)/$1.($2+1)/e; #well, it works.
-
- my %hash = $soa->hash;
- $hash{recdata} = $data;
- my $new = new FS::domain_record \%hash;
- $new->replace($soa);
-}
-
-=item svc_domain
-
-Returns the domain (see L<FS::svc_domain>) for this record.
-
-=cut
-
-sub svc_domain {
- my $self = shift;
- qsearchs('svc_domain', { svcnum => $self->svcnum } );
-}
-
-=item zone
-
-Returns the canonical zone name.
-
-=cut
-
-sub zone {
- my $self = shift;
- my $zone = $self->reczone; # or die ?
- if ( $zone =~ /\.$/ ) {
- $zone =~ s/\.$//;
- } else {
- my $svc_domain = $self->svc_domain; # or die ?
- $zone .= '.'. $svc_domain->domain;
- $zone =~ s/^\@\.//;
- }
- $zone;
-}
-
-=item reverse_record
-
-Returns the corresponding reverse-ARPA record as another FS::domain_record
-object. If the specific record does not exist in the database but the
-reverse-ARPA zone itself does, an appropriate new record is created. If no
-reverse-ARPA zone is available at all, returns false.
-
-(You can test whether or not record itself exists in the database or is a new
-object that might need to be inserted by checking the recnum field)
-
-Mostly used by the insert and delete methods - probably should see them for
-examples.
-
-=cut
-
-sub reverse_record {
- my $self = shift;
- warn "reverse_record called\n" if $DEBUG;
- #should support classless reverse-ARPA ala rfc2317 too
- $self->recdata =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
- or return '';
- my $domain = "$3.$2.$1.in-addr.arpa";
- my $ptr_reczone = $4;
- warn "reverse_record: searching for domain: $domain\n" if $DEBUG;
- my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
- or return '';
- warn "reverse_record: found domain: $domain\n" if $DEBUG;
- my %hash = (
- 'svcnum' => $svc_domain->svcnum,
- 'reczone' => $ptr_reczone,
- 'recaf' => 'IN',
- 'rectype' => 'PTR',
- );
- qsearchs('domain_record', \%hash )
- or new FS::domain_record { %hash, 'recdata' => $self->zone.'.' };
-}
-
-=back
-
-=head1 BUGS
-
-The data validation doesn't check everything it could. In particular,
-there is no protection against bad data that passes the regex, duplicate
-SOA records, forgetting the trailing `.', impossible IP addersses, etc. Of
-course, it's still better than editing the zone files directly. :)
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/export_svc.pm b/FS/FS/export_svc.pm
deleted file mode 100644
index 0370f5f..0000000
--- a/FS/FS/export_svc.pm
+++ /dev/null
@@ -1,322 +0,0 @@
-package FS::export_svc;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs dbh );
-use FS::part_export;
-use FS::part_svc;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::export_svc - Object methods for export_svc records
-
-=head1 SYNOPSIS
-
- use FS::export_svc;
-
- $record = new FS::export_svc \%hash;
- $record = new FS::export_svc { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::export_svc object links a service definition (see L<FS::part_svc>) to
-an export (see L<FS::part_export>). FS::export_svc inherits from FS::Record.
-The following fields are currently supported:
-
-=over 4
-
-=item exportsvcnum - primary key
-
-=item exportnum - export (see L<FS::part_export>)
-
-=item svcpart - service definition (see L<FS::part_svc>)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'export_svc'; }
-
-=item insert [ JOB, OFFSET, MULTIPLIER ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-TODOC: JOB, OFFSET, MULTIPLIER
-
-=cut
-
-sub insert {
- my $self = shift;
- my( $job, $offset, $mult ) = ( '', 0, 100);
- $job = shift if @_;
- $offset = shift if @_;
- $mult = shift if @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->check;
- return $error if $error;
-
- #check for duplicates!
- my @checks = ();
- my $svcdb = $self->part_svc->svcdb;
- if ( $svcdb eq 'svc_acct' ) {
-
- if ( $self->part_export->nodomain =~ /^Y/i ) {
- push @checks, {
- label => 'usernames',
- method => 'username',
- sortby => sub { $a cmp $b },
- };
- } else {
- push @checks, {
- label => 'username@domain',
- method => 'email',
- sortby => sub {
- my($auser, $adomain) = split('@', $a);
- my($buser, $bdomain) = split('@', $b);
- $adomain cmp $bdomain || $auser cmp $buser;
- },
- };
- }
-
- unless ( $self->part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
- push @checks, {
- label => 'uids',
- method => 'uid',
- sortby => sub { $a <=> $b },
- };
- }
-
- } elsif ( $svcdb eq 'svc_domain' ) {
- push @checks, {
- label => 'domains',
- method => 'domain',
- sortby => sub { $a cmp $b },
- };
- } else {
- warn "WARNING: No duplicate checking done on merge of $svcdb exports";
- }
-
- if ( @checks ) {
-
- my $done = 0;
- my $percheck = $mult / scalar(@checks);
-
- foreach my $check ( @checks ) {
-
- if ( $job ) {
- $error = $job->update_statustext(int( $offset + ($done+.33) *$percheck ));
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my @current_svc = $self->part_export->svc_x;
- #warn "current: ". scalar(@current_svc). " $current_svc[0]\n";
-
- if ( $job ) {
- $error = $job->update_statustext(int( $offset + ($done+.67) *$percheck ));
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my @new_svc = $self->part_svc->svc_x;
- #warn "new: ". scalar(@new_svc). " $new_svc[0]\n";
-
- if ( $job ) {
- $error = $job->update_statustext(int( $offset + ($done+1) *$percheck ));
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $method = $check->{'method'};
- my %cur_svc = map { $_->$method() => $_ } @current_svc;
- my @dup_svc = grep { $cur_svc{$_->$method()} } @new_svc;
- #my @diff_customer = grep {
- # $_->cust_pkg->custnum != $cur_svc{$_->$method()}->cust_pkg->custnum
- # } @dup_svc;
-
-
-
- if ( @dup_svc ) { #aye, that's the rub
- #error out for now, eventually accept different options of adjustments
- # to make to allow us to continue forward
- $dbh->rollback if $oldAutoCommit;
-
- my @diff_customer_svc = grep {
- my $cust_pkg = $_->cust_svc->cust_pkg;
- my $custnum = $cust_pkg ? $cust_pkg->custnum : 0;
- my $other_cust_pkg = $cur_svc{$_->$method()}->cust_svc->cust_pkg;
- my $other_custnum = $other_cust_pkg ? $other_cust_pkg->custnum : 0;
- $custnum != $other_custnum;
- } @dup_svc;
-
- my $label = $check->{'label'};
- my $sortby = $check->{'sortby'};
- return "Can't export ".
- $self->part_svc->svcpart.':'.$self->part_svc->svc. " service to ".
- $self->part_export->exportnum.':'.$self->part_export->exporttype.
- ' on '. $self->part_export->machine.
- ' : '. scalar(@dup_svc). " duplicate $label".
- ' ('. scalar(@diff_customer_svc). " from different customers)".
- ": ". join(', ', sort $sortby map { $_->$method() } @dup_svc )
- #": ". join(', ', sort $sortby map { $_->$method() } @diff_customer_svc )
- ;
- }
-
- $done++;
- }
-
- } #end of duplicate check, whew
-
- $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
-# if ( $self->part_svc->svcdb eq 'svc_acct' ) {
-#
-# if ( $self->part_export->nodomain =~ /^Y/i ) {
-#
-# select username from svc_acct where svcpart = $svcpart
-# group by username having count(*) > 1;
-#
-# } else {
-#
-# select username, domain
-# from svc_acct
-# join svc_domain on ( svc_acct.domsvc = svc_domain.svcnum )
-# group by username, domain having count(*) > 1;
-#
-# }
-#
-# } elsif ( $self->part_svc->svcdb eq 'svc_domain' ) {
-#
-# #similar but easier domain checking one
-#
-# } #etc.?
-#
-# my @services =
-# map { $_->part_svc }
-# grep { $_->svcpart != $self->svcpart }
-# $self->part_export->export_svc;
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('exportsvcnum')
- || $self->ut_number('exportnum')
- || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum')
- || $self->ut_number('svcpart')
- || $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart')
- || $self->SUPER::check
- ;
-}
-
-=item part_export
-
-Returns the FS::part_export object (see L<FS::part_export>).
-
-=cut
-
-sub part_export {
- my $self = shift;
- qsearchs( 'part_export', { 'exportnum' => $self->exportnum } );
-}
-
-=item part_svc
-
-Returns the FS::part_svc object (see L<FS::part_svc>).
-
-=cut
-
-sub part_svc {
- my $self = shift;
- qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::part_export>, L<FS::part_svc>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_Common.pm b/FS/FS/h_Common.pm
deleted file mode 100644
index ca13e1b..0000000
--- a/FS/FS/h_Common.pm
+++ /dev/null
@@ -1,124 +0,0 @@
-package FS::h_Common;
-
-use strict;
-use FS::Record qw(dbdef);
-use Carp qw(confess);
-
-=head1 NAME
-
-FS::h_Common - History table "mixin" common base class
-
-=head1 SYNOPSIS
-
-package FS::h_tablename;
-@ISA = qw( FS::h_Common FS::tablename );
-
-sub table { 'h_table_name'; }
-
-sub insert { return "can't insert history records manually"; }
-sub delete { return "can't delete history records"; }
-sub replace { return "can't modify history records"; }
-
-=head1 DESCRIPTION
-
-FS::h_Common is intended as a "mixin" base class for history table classes to
-inherit from.
-
-=head1 METHODS
-
-=over 4
-
-=item sql_h_search END_TIMESTAMP [ START_TIMESTAMP ]
-
-Returns an a list consisting of the "SELECT", "EXTRA_SQL", SQL fragments, a
-placeholder for "CACHE_OBJ" and an "AS" SQL fragment, to search for the
-appropriate history records created before END_TIMESTAMP and (optionally) not
-deleted before START_TIMESTAMP.
-
-=cut
-
-sub sql_h_search {
- my( $self, $end ) = ( shift, shift );
-
- my $table = $self->table;
- my $real_table = ($table =~ /^h_(.*)$/) ? $1 : $table;
- my $pkey = dbdef->table($real_table)->primary_key
- or die "can't (yet) search history table $real_table without a primary key";
-
- unless ($end) {
- confess 'Called sql_h_search without END_TIMESTAMP';
- }
-
- my( $notdeleted, $notdeleted_mr ) = ( '', '' );
- if ( scalar(@_) && $_[0] ) {
- $notdeleted =
- "AND 0 = ( SELECT COUNT(*) FROM $table as notdel
- WHERE notdel.$pkey = maintable.$pkey
- AND notdel.history_action = 'delete'
- AND notdel.history_date > maintable.history_date
- AND notdel.history_date <= $_[0]
- )";
- $notdeleted_mr =
- "AND 0 = ( SELECT COUNT(*) FROM $table as notdel_mr
- WHERE notdel_mr.$pkey = mostrecent.$pkey
- AND notdel_mr.history_action = 'delete'
- AND notdel_mr.history_date > mostrecent.history_date
- AND notdel_mr.history_date <= $_[0]
- )";
- }
-
- (
- #"DISTINCT ON ( $pkey ) *",
- "*",
-
- "AND history_date <= $end
- AND ( history_action = 'insert'
- OR history_action = 'replace_new'
- )
- $notdeleted
- AND history_date = ( SELECT MAX(mostrecent.history_date)
- FROM $table AS mostrecent
- WHERE mostrecent.$pkey = maintable.$pkey
- AND mostrecent.history_date <= $end
- AND ( mostrecent.history_action = 'insert'
- OR mostrecent.history_action = 'replace_new'
- )
- $notdeleted_mr
- )
-
- ORDER BY $pkey ASC",
- #ORDER BY $pkey ASC, history_date DESC",
-
- '',
-
- 'AS maintable',
- );
-
-}
-
-=item sql_h_searchs END_TIMESTAMP [ START_TIMESTAMP ]
-
-Like sql_h_search, but limited to the single most recent record (before
-END_TIMESTAMP)
-
-=cut
-
-sub sql_h_searchs {
- my $self = shift;
- my($select, $where, $cacheobj, $as) = $self->sql_h_search(@_);
- $where .= ' LIMIT 1';
- ($select, $where, $cacheobj, $as);
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_cust_bill.pm b/FS/FS/h_cust_bill.pm
deleted file mode 100644
index 7a3d811..0000000
--- a/FS/FS/h_cust_bill.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::h_cust_bill;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::cust_bill;
-
-@ISA = qw( FS::h_Common FS::cust_bill );
-
-sub table { 'h_cust_bill' };
-
-=head1 NAME
-
-FS::h_cust_bill - Historical record of customer tax changes (old-style)
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_cust_bill object represents historical changes to invoices.
-FS::h_cust_bill inherits from FS::h_Common and FS::cust_bill.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::cust_bill>, L<FS::h_Common>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_cust_credit.pm b/FS/FS/h_cust_credit.pm
deleted file mode 100644
index 1425a26..0000000
--- a/FS/FS/h_cust_credit.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::h_cust_credit;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::cust_credit;
-
-@ISA = qw( FS::h_Common FS::cust_credit );
-
-sub table { 'h_cust_credit' };
-
-=head1 NAME
-
-FS::h_cust_credit - Historical record of customer credit changes
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_cust_credit object represents historical changes to credits.
-FS::h_cust_credit inherits from FS::h_Common and FS::cust_credit.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::cust_credit>, L<FS::h_Common>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_cust_pay.pm b/FS/FS/h_cust_pay.pm
deleted file mode 100644
index 6434b3f..0000000
--- a/FS/FS/h_cust_pay.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::h_cust_pay;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::cust_pay;
-
-@ISA = qw( FS::h_Common FS::cust_pay );
-
-sub table { 'h_cust_pay' };
-
-=head1 NAME
-
-FS::h_cust_pay - Historical record of customer payment changes
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_cust_pay object represents historical changes to payments.
-FS::h_cust_pay inherits from FS::h_Common and FS::cust_pay.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::cust_pay>, L<FS::h_Common>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_cust_svc.pm b/FS/FS/h_cust_svc.pm
deleted file mode 100644
index 921be3a..0000000
--- a/FS/FS/h_cust_svc.pm
+++ /dev/null
@@ -1,161 +0,0 @@
-package FS::h_cust_svc;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Carp;
-use FS::Record qw(qsearchs);
-use FS::h_Common;
-use FS::cust_svc;
-
-@ISA = qw( FS::h_Common FS::cust_svc );
-
-$DEBUG = 0;
-
-sub table { 'h_cust_svc'; }
-
-=head1 NAME
-
-FS::h_cust_svc - Object method for h_cust_svc objects
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_cust_svc object represents a historical service. FS::h_cust_svc
-inherits from FS::h_Common and FS::cust_svc.
-
-=head1 METHODS
-
-=over 4
-
-=item date_deleted
-
-Returns the date this service was deleted, if any.
-
-=cut
-
-sub date_deleted {
- my $self = shift;
- $self->h_date('delete');
-}
-
-=item label END_TIMESTAMP [ START_TIMESTAMP ]
-
-Returns a label for this historical service, if the service was created before
-END_TIMESTAMP and (optionally) not deleted before START_TIMESTAMP. Otherwise,
-returns an empty list.
-
-If a service is found, returns a list consisting of:
-- The name of this historical service (from part_svc)
-- A meaningful identifier (username, domain, or mail alias)
-- The table name (i.e. svc_domain) for this historical service
-
-=cut
-
-sub label {
- my $self = shift;
- carp "FS::h_cust_svc::label called on $self" if $DEBUG;
- my $svc_x = $self->h_svc_x(@_);
- return () unless $svc_x;
- my $part_svc = $self->part_svc;
-
- unless ($svc_x) {
- carp "can't find h_". $self->part_svc->svcdb. '.svcnum '. $self->svcnum if $DEBUG;
- return $part_svc->svc, 'n/a', $part_svc->svcdb;
- }
-
- my @label;
- eval { @label = $self->_svc_label($svc_x, @_); };
-
- if ($@) {
- carp 'while resolving history record for svcdb/svcnum ' .
- $part_svc->svcdb . '/' . $self->svcnum . ': ' . $@ if $DEBUG;
- return $part_svc->svc, 'n/a', $part_svc->svcdb;
- } else {
- return @label;
- }
-
-}
-
-=item h_svc_x END_TIMESTAMP [ START_TIMESTAMP ]
-
-Returns the FS::h_svc_XXX object for this service as of END_TIMESTAMP (i.e. an
-FS::h_svc_acct object or FS::h_svc_domain object, etc.) and (optionally) not
-cancelled before START_TIMESTAMP.
-
-=cut
-
-#false laziness w/cust_pkg::h_cust_svc
-sub h_svc_x {
- my $self = shift;
- my $svcdb = $self->part_svc->svcdb;
-
- warn "requiring FS/h_$svcdb.pm" if $DEBUG;
- require "FS/h_$svcdb.pm";
- my $svc_x = qsearchs(
- "h_$svcdb",
- { 'svcnum' => $self->svcnum, },
- "FS::h_$svcdb"->sql_h_searchs(@_),
- ) || $self->SUPER::svc_x;
-
- if ($svc_x) {
- carp "Using $svcdb in place of missing h_${svcdb} record."
- if ($svc_x->isa('FS::' . $svcdb) and $DEBUG);
- return $svc_x;
- } else {
- return '';
- }
-
-}
-
-# _upgrade_data
-#
-# Used by FS::Upgrade to migrate to a new database.
-#
-#
-
-use FS::UID qw( driver_name dbh );
-
-sub _upgrade_data { # class method
- my ($class, %opts) = @_;
-
- warn "[FS::h_cust_svc] upgrading $class\n" if $DEBUG;
-
- return if driver_name =~ /^mysql/; #You can't specify target table 'h_cust_svc' for update in FROM clause
-
- my $sql = "
- DELETE FROM h_cust_svc
- WHERE history_action = 'delete'
- AND historynum != ( SELECT min(historynum) FROM h_cust_svc AS main
- WHERE main.history_date = h_cust_svc.history_date
- AND main.history_user = h_cust_svc.history_user
- AND main.svcnum = h_cust_svc.svcnum
- AND main.svcpart = h_cust_svc.svcpart
- AND ( main.pkgnum = h_cust_svc.pkgnum
- OR ( main.pkgnum IS NULL AND h_cust_svc.pkgnum IS NULL )
- )
- AND ( main.overlimit = h_cust_svc.overlimit
- OR ( main.overlimit IS NULL AND h_cust_svc.overlimit IS NULL )
- )
- )
- ";
-
- warn $sql if $DEBUG;
- my $sth = dbh->prepare($sql) or die dbh->errstr;
- $sth->execute or die $sth->errstr;
-
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::cust_svc>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_cust_tax_exempt.pm b/FS/FS/h_cust_tax_exempt.pm
deleted file mode 100644
index 9d2318b..0000000
--- a/FS/FS/h_cust_tax_exempt.pm
+++ /dev/null
@@ -1,40 +0,0 @@
-package FS::h_cust_tax_exempt;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::cust_tax_exempt;
-
-@ISA = qw( FS::h_Common FS::cust_tax_exempt );
-
-sub table { 'h_cust_tax_exempt' };
-
-=head1 NAME
-
-FS::h_cust_tax_exempt - Historical record of customer tax changes (old-style)
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_cust_tax_exempt object represents historical changes to old-style
-customer tax exemptions. FS::h_cust_tax_exempt inherits from FS::h_Common and
-FS::cust_tax_exempt.
-
-=head1 NOTE
-
-Old-style customer tax exemptions are only useful for legacy migrations - if
-you are looking for current customer tax exemption data see
-L<FS::cust_tax_exempt_pkg>.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::cust_tax_exempt>, L<FS::cust_tax_exempt_pkg>, L<FS::h_Common>,
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_domain_record.pm b/FS/FS/h_domain_record.pm
deleted file mode 100644
index 0ab974f..0000000
--- a/FS/FS/h_domain_record.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::h_domain_record;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::domain_record;
-
-@ISA = qw( FS::h_Common FS::domain_record );
-
-sub table { 'h_domain_record' };
-
-=head1 NAME
-
-FS::h_domain_record - Historical DNS entry objects
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_domain_record object represents a historical entry in a DNS zone.
-FS::h_domain_record inherits from FS::h_Common and FS::domain_record.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::svc_external>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_svc_acct.pm b/FS/FS/h_svc_acct.pm
deleted file mode 100644
index 247d20c..0000000
--- a/FS/FS/h_svc_acct.pm
+++ /dev/null
@@ -1,78 +0,0 @@
-package FS::h_svc_acct;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Carp qw(carp);
-use FS::Record qw(qsearchs);
-use FS::h_Common;
-use FS::svc_acct;
-use FS::svc_domain;
-use FS::h_svc_domain;
-
-@ISA = qw( FS::h_Common FS::svc_acct );
-
-$DEBUG = 0;
-
-sub table { 'h_svc_acct' };
-
-=head1 NAME
-
-FS::h_svc_acct - Historical account objects
-
-=head1 SYNOPSIS
-
-=head1 METHODS
-
-=over 4
-
-=item svc_domain
-
-=cut
-
-sub svc_domain {
- my $self = shift;
- qsearchs( 'h_svc_domain',
- { 'svcnum' => $self->domsvc },
- FS::h_svc_domain->sql_h_searchs(@_),
- );
-}
-
-=item domain
-
-Returns the domain associated with this account.
-
-=cut
-
-sub domain {
- my $self = shift;
- die "svc_acct.domsvc is null for svcnum ". $self->svcnum unless $self->domsvc;
-
- my $svc_domain = $self->svc_domain(@_) || $self->SUPER::svc_domain()
- or die 'no history svc_domain.svcnum for svc_acct.domsvc ' . $self->domsvc;
-
- carp 'Using FS::svc_acct record in place of missing FS::h_svc_acct record.'
- if ($svc_domain->isa('FS::svc_acct') and $DEBUG);
-
- $svc_domain->domain;
-
-}
-
-
-=back
-
-=head1 DESCRIPTION
-
-An FS::h_svc_acct object represents a historical account. FS::h_svc_acct
-inherits from FS::h_Common and FS::svc_acct.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::svc_acct>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_svc_broadband.pm b/FS/FS/h_svc_broadband.pm
deleted file mode 100644
index d6038fb..0000000
--- a/FS/FS/h_svc_broadband.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::h_svc_broadband;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::svc_broadband;
-
-@ISA = qw( FS::h_Common FS::svc_broadband );
-
-sub table { 'h_svc_broadband' };
-
-=head1 NAME
-
-FS::h_svc_broadband - Historical broadband connection objects
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_svc_broadband object represents a historical broadband connection.
-FS::h_svc_broadband inherits from FS::h_Common and FS::svc_broadband.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::svc_broadband>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_svc_domain.pm b/FS/FS/h_svc_domain.pm
deleted file mode 100644
index 60d54f7..0000000
--- a/FS/FS/h_svc_domain.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::h_svc_domain;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::svc_domain;
-
-@ISA = qw( FS::h_Common FS::svc_domain );
-
-sub table { 'h_svc_domain' };
-
-=head1 NAME
-
-FS::h_svc_domain - Historical domain objects
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_svc_domain object represents a historical domain. FS::h_svc_domain
-inherits from FS::h_Common and FS::svc_domain.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::svc_domain>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_svc_external.pm b/FS/FS/h_svc_external.pm
deleted file mode 100644
index 5eb7064..0000000
--- a/FS/FS/h_svc_external.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::h_svc_external;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::svc_external;
-
-@ISA = qw( FS::h_Common FS::svc_external );
-
-sub table { 'h_svc_external' };
-
-=head1 NAME
-
-FS::h_svc_external - Historical externally tracked service objects
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_svc_external object represents a historical externally tracked service.
-FS::h_svc_external inherits from FS::h_Common and FS::svc_external.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::svc_external>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_svc_forward.pm b/FS/FS/h_svc_forward.pm
deleted file mode 100644
index 25b2039..0000000
--- a/FS/FS/h_svc_forward.pm
+++ /dev/null
@@ -1,85 +0,0 @@
-package FS::h_svc_forward;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use FS::Record qw(qsearchs);
-use FS::h_Common;
-use FS::svc_forward;
-use FS::svc_acct;
-use FS::h_svc_acct;
-
-use Carp qw(carp);
-
-$DEBUG = 0;
-
-@ISA = qw( FS::h_Common FS::svc_forward );
-
-sub table { 'h_svc_forward' };
-
-=head1 NAME
-
-FS::h_svc_forward - Historical mail forwarding alias objects
-
-=head1 SYNOPSIS
-
-=head1 METHODS
-
-=over 4
-
-=item srcsvc_acct
-
-=cut
-
-sub srcsvc_acct {
- my $self = shift;
- my $h_svc_acct = qsearchs(
- 'h_svc_acct',
- { 'svcnum' => $self->srcsvc },
- FS::h_svc_acct->sql_h_searchs(@_),
- ) || $self->SUPER::srcsvc_acct
- or die "no history svc_acct.svcnum for svc_forward.srcsvc ". $self->srcsvc;
-
- carp 'Using svc_acct in place of missing h_svc_acct record.'
- if ($h_svc_acct->isa('FS::domain_record') and $DEBUG);
-
- return $h_svc_acct;
-
-}
-
-=item dstsvc_acct
-
-=cut
-
-sub dstsvc_acct {
- my $self = shift;
- my $h_svc_acct = qsearchs(
- 'h_svc_acct',
- { 'svcnum' => $self->dstsvc },
- FS::h_svc_acct->sql_h_searchs(@_),
- ) || $self->SUPER::dstsvc_acct
- or die "no history svc_acct.svcnum for svc_forward.dstsvc ". $self->dstsvc;
-
- carp 'Using svc_acct in place of missing h_svc_acct record.'
- if ($h_svc_acct->isa('FS::domain_record') and $DEBUG);
-
- return $h_svc_acct;
-}
-
-=back
-
-=head1 DESCRIPTION
-
-An FS::h_svc_forward object represents a historical mail forwarding alias.
-FS::h_svc_forward inherits from FS::h_Common and FS::svc_forward.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::svc_forward>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_svc_phone.pm b/FS/FS/h_svc_phone.pm
deleted file mode 100644
index 95898c7..0000000
--- a/FS/FS/h_svc_phone.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::h_svc_phone;
-
-use strict;
-use vars qw( @ISA );
-use FS::h_Common;
-use FS::svc_phone;
-
-@ISA = qw( FS::h_Common FS::svc_phone );
-
-sub table { 'h_svc_phone' };
-
-=head1 NAME
-
-FS::h_svc_phone - Historical phone number objects
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-An FS::h_svc_phone object represents a historical phone number.
-FS::h_svc_phone inherits from FS::h_Common and FS::svc_phone.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::svc_phone>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/h_svc_www.pm b/FS/FS/h_svc_www.pm
deleted file mode 100644
index 2a3b6dc..0000000
--- a/FS/FS/h_svc_www.pm
+++ /dev/null
@@ -1,67 +0,0 @@
-package FS::h_svc_www;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Carp qw(carp);
-use FS::Record qw(qsearchs);
-use FS::h_Common;
-use FS::svc_www;
-use FS::h_domain_record;
-
-@ISA = qw( FS::h_Common FS::svc_www );
-
-$DEBUG = 0;
-
-sub table { 'h_svc_www' };
-
-=head1 NAME
-
-FS::h_svc_www - Historical web virtual host objects
-
-=head1 SYNOPSIS
-
-=head1 METHODS
-
-=over 4
-
-=item domain_record
-
-=cut
-
-sub domain_record {
- my $self = shift;
-
- carp 'Called FS::h_svc_www->domain_record on svcnum ' . $self->svcnum if $DEBUG;
-
- my $domain_record = qsearchs(
- 'h_domain_record',
- { 'recnum' => $self->recnum },
- FS::h_domain_record->sql_h_searchs(@_),
- ) || $self->SUPER::domain_record
- or die "no history domain_record.recnum for svc_www.recnum ". $self->domsvc;
-
- carp 'Using domain_record in place of missing h_domain_record record.'
- if ($domain_record->isa('FS::domain_record') and $DEBUG);
-
- return $domain_record;
-
-}
-
-=back
-
-=head1 DESCRIPTION
-
-An FS::h_svc_www object represents a historical web virtual host.
-FS::h_svc_www inherits from FS::h_Common and FS::svc_www.
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::h_Common>, L<FS::svc_www>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/inventory_class.pm b/FS/FS/inventory_class.pm
deleted file mode 100644
index 508889b..0000000
--- a/FS/FS/inventory_class.pm
+++ /dev/null
@@ -1,164 +0,0 @@
-package FS::inventory_class;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( dbh qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::inventory_class - Object methods for inventory_class records
-
-=head1 SYNOPSIS
-
- use FS::inventory_class;
-
- $record = new FS::inventory_class \%hash;
- $record = new FS::inventory_class { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::inventory_class object represents a class of inventory, such as "DID
-numbers" or "physical equipment serials". FS::inventory_class inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item classnum - primary key
-
-=item classname - Name of this class
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new inventory class. To add the class to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'inventory_class'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid inventory class. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('classnum')
- || $self->ut_textn('classname')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item num_avail
-
-Returns the number of available (unused/unallocated) inventory items of this
-class (see L<FS::inventory_item>).
-
-=cut
-
-sub num_avail {
- shift->num_sql('( svcnum IS NULL OR svcnum = 0 )');
-}
-
-sub num_sql {
- my( $self, $sql ) = @_;
- $sql = "AND $sql" if length($sql);
- my $statement =
- "SELECT COUNT(*) FROM inventory_item WHERE classnum = ? $sql";
- my $sth = dbh->prepare($statement) or die dbh->errstr. " preparing $statement";
- $sth->execute($self->classnum) or die $sth->errstr. " executing $statement";
- $sth->fetchrow_arrayref->[0];
-}
-
-=item num_used
-
-Returns the number of used (allocated) inventory items of this class (see
-L<FS::inventory_class>).
-
-=cut
-
-sub num_used {
- shift->num_sql("svcnum IS NOT NULL AND svcnum > 0 ");
-}
-
-=item num_total
-
-Returns the total number of inventory items of this class (see
-L<FS::inventory_class>).
-
-=cut
-
-sub num_total {
- shift->num_sql('');
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::inventory_item>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/inventory_item.pm b/FS/FS/inventory_item.pm
deleted file mode 100644
index 7fa350f..0000000
--- a/FS/FS/inventory_item.pm
+++ /dev/null
@@ -1,204 +0,0 @@
-package FS::inventory_item;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( dbh qsearch qsearchs );
-use FS::cust_main_Mixin;
-use FS::inventory_class;
-use FS::cust_svc;
-
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
-=head1 NAME
-
-FS::inventory_item - Object methods for inventory_item records
-
-=head1 SYNOPSIS
-
- use FS::inventory_item;
-
- $record = new FS::inventory_item \%hash;
- $record = new FS::inventory_item { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::inventory_item object represents a specific piece of (real or virtual)
-inventory, such as a specific DID or serial number. FS::inventory_item
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item itemnum - primary key
-
-=item classnum - Inventory class (see L<FS::inventory_class>)
-
-=item item - Item identifier (unique within its inventory class)
-
-=item svcnum - Customer servcie (see L<FS::cust_svc>)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new item. To add the item to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'inventory_item'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid item. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('itemnum')
- || $self->ut_foreign_key('classnum', 'inventory_class', 'classnum' )
- || $self->ut_text('item')
- || $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum' )
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item cust_svc
-
-Returns the customer service associated with this inventory item, if the
-item has been used (see L<FS::cust_svc>).
-
-=cut
-
-sub cust_svc {
- my $self = shift;
- return '' unless $self->svcnum;
- qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } );
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item batch_import
-
-=cut
-
-sub batch_import {
- my $param = shift;
-
- my $fh = $param->{filehandle};
-
- my $imported = 0;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $line;
- while ( defined($line=<$fh>) ) {
-
- chomp $line;
-
- my $inventory_item = new FS::inventory_item {
- 'classnum' => $param->{'classnum'},
- 'item' => $line,
- };
-
- my $error = $inventory_item->insert;
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
-
- #or just skip?
- #next;
- }
-
- $imported++;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- #might want to disable this if we skip records for any reason...
- return "Empty file!" unless $imported;
-
- '';
-
-}
-
-=back
-
-=head1 BUGS
-
-maybe batch_import should be a regular method in FS::inventory_class
-
-=head1 SEE ALSO
-
-L<inventory_class>, L<cust_svc>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/m2m_Common.pm b/FS/FS/m2m_Common.pm
deleted file mode 100644
index 5dc2a8e..0000000
--- a/FS/FS/m2m_Common.pm
+++ /dev/null
@@ -1,144 +0,0 @@
-package FS::m2m_Common;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use FS::Schema qw( dbdef );
-use FS::Record qw( qsearch qsearchs dbh );
-
-#hmm. well. we seem to be used as a mixin.
-#@ISA = qw( FS::Record );
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::m2m_Common - Mixin class for classes in a many-to-many relationship
-
-=head1 SYNOPSIS
-
-use FS::m2m_Common;
-
-@ISA = qw( FS::m2m_Common FS::Record );
-
-=head1 DESCRIPTION
-
-FS::m2m_Common is intended as a mixin class for classes which have a
-many-to-many relationship with another table (via a linking table).
-
-Note: It is currently assumed that the link table contains two fields
-named the same as the primary keys of ths base and target tables.
-
-=head1 METHODS
-
-=over 4
-
-=item process_m2m OPTION => VALUE, ...
-
-Available options:
-
-link_table (required) -
-
-target_table (required) -
-
-params (required) - hashref; keys are primary key values in target_table (values are boolean). For convenience, keys may optionally be prefixed with the name
-of the primary key, as in agentnum54 instead of 54, or passed as an arrayref
-of values.
-
-=cut
-
-sub process_m2m {
- my( $self, %opt ) = @_;
-
- my $self_pkey = $self->dbdef_table->primary_key;
- my %hash = ( $self_pkey => $self->$self_pkey() );
-
- my $link_table = $self->_load_table($opt{'link_table'});
-
- my $target_table = $self->_load_table($opt{'target_table'});
- my $target_pkey = dbdef->table($target_table)->primary_key;
-
- if ( ref($opt{'params'}) eq 'ARRAY' ) {
- $opt{'params'} = { map { $_=>1 } @{$opt{'params'}} };
- }
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $del_obj (
- grep {
- my $targetnum = $_->$target_pkey();
- ( ! $opt{'params'}->{$targetnum}
- && ! $opt{'params'}->{"$target_pkey$targetnum"}
- );
- }
- qsearch( $link_table, \%hash )
- ) {
- my $error = $del_obj->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- foreach my $add_targetnum (
- grep { ! qsearchs( $link_table, { %hash, $target_pkey => $_ } ) }
- map { /^($target_pkey)?(\d+)$/; $2; }
- grep { /^($target_pkey)?(\d+)$/ }
- grep { $opt{'params'}->{$_} }
- keys %{ $opt{'params'} }
- ) {
-
- my $add_obj = "FS::$link_table"->new( {
- %hash,
- $target_pkey => $add_targetnum,
- });
- my $error = $add_obj->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-sub _load_table {
- my( $self, $table ) = @_;
- eval "use FS::$table";
- die $@ if $@;
- $table;
-}
-
-#=item target_table
-#
-#=cut
-#
-#sub target_table {
-# my $self = shift;
-# my $target_table = $self->_target_table;
-# eval "use FS::$target_table";
-# die $@ if $@;
-# $target_table;
-#}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/FS/m2name_Common.pm b/FS/FS/m2name_Common.pm
deleted file mode 100644
index e9dcee9..0000000
--- a/FS/FS/m2name_Common.pm
+++ /dev/null
@@ -1,177 +0,0 @@
-package FS::m2name_Common;
-
-use strict;
-use vars qw( $DEBUG $me );
-use Carp;
-use FS::Schema qw( dbdef );
-use FS::Record qw( qsearchs ); #qsearch dbh );
-
-$DEBUG = 0;
-
-$me = '[FS::m2name_Common]';
-
-=head1 NAME
-
-FS::m2name_Common - Mixin class for tables with a related table listing names
-
-=head1 SYNOPSIS
-
-use FS::m2name_Common;
-
-@ISA = qw( FS::m2name_Common FS::Record );
-
-=head1 DESCRIPTION
-
-FS::m2name_Common is intended as a mixin class for classes which have a
-related table that lists names.
-
-=head1 METHODS
-
-=over 4
-
-=item process_m2name OPTION => VALUE, ...
-
-Available options:
-
-link_table (required) - Table into which the records are inserted.
-
-num_col (optional) - Column in link_table which links to the primary key of the base table. If not specified, it is assumed this has the same name.
-
-name_col (required) - Name of the column in link_table that stores the string names.
-
-names_list (required) - List reference of the possible string name values.
-
-params (required) - Hashref of keys and values, often passed as C<scalar($cgi->Vars)> from a form. Processing is controlled by the B<param_style param> option.
-
-param_style (required) - Controls processing of B<params>. I<'link_table.value checkboxes'> specifies that parameters keys are in the form C<link_table.name>, and the values are booleans controlling whether or not to insert that name into link_table. I<'name_colN values'> specifies that parameter keys are in the form C<name_col0>, C<name_col1>, and so on, and values are the names inserted into link_table.
-
-args_callback (optional) - Coderef. Optional callback that may modify arguments for insert and replace operations. The callback is run with four arguments: the first argument is object being inserted or replaced (i.e. FS::I<link_table> object), the second argument is a prefix to use when retreiving CGI arguements from the params hashref, the third argument is the params hashref (see above), and the final argument is a listref of arguments that the callback should modify.
-
-=cut
-
-sub process_m2name {
- my( $self, %opt ) = @_;
-
- my $self_pkey = $self->dbdef_table->primary_key;
- my $link_sourcekey = $opt{'num_col'} || $self_pkey;
-
- my $link_table = $self->_load_table($opt{'link_table'});
-
- my $link_static = $opt{'link_static'} || {};
-
- warn "$me processing m2name from ". $self->table. ".$link_sourcekey".
- " to $link_table\n"
- if $DEBUG;
-
- foreach my $name ( @{ $opt{'names_list'} } ) {
-
- warn "$me checking $name\n" if $DEBUG;
-
- my $name_col = $opt{'name_col'};
-
- my $obj = qsearchs( $link_table, {
- $link_sourcekey => $self->$self_pkey(),
- $name_col => $name,
- %$link_static,
- });
-
- my $param = '';
- my $prefix = '';
- if ( $opt{'param_style'} =~ /link_table.value\s+checkboxes/i ) {
- #access_group.html style
- my $paramname = "$link_table.$name";
- $param = $opt{'params'}->{$paramname};
- } elsif ( $opt{'param_style'} =~ /name_colN values/i ) {
- #part_event.html style
-
- my @fields = grep { /^$name_col\d+$/ }
- keys %{$opt{'params'}};
-
- $param = grep { $name eq $opt{'params'}->{$_} } @fields;
-
- if ( $param ) {
- #this depends on their being one condition per name...
- #which needs to be enforced on the edit page...
- #(it is on part_event and access_group edit)
- foreach my $field (@fields) {
- $prefix = "$field." if $name eq $opt{'params'}->{$field};
- }
- warn "$me prefix $prefix\n" if $DEBUG;
- }
- } else { #??
- croak "unknown param_style: ". $opt{'param_style'};
- $param = $opt{'params'}->{$name};
- }
-
- if ( $obj && ! $param ) {
-
- warn "$me deleting $name\n" if $DEBUG;
-
- my $d_obj = $obj; #need to save $obj for below.
- my $error = $d_obj->delete;
- die "error deleting $d_obj for $link_table.$name: $error" if $error;
-
- } elsif ( $param && ! $obj ) {
-
- warn "$me inserting $name\n" if $DEBUG;
-
- #ok to clobber it now (but bad form nonetheless?)
- #$obj = new "FS::$link_table" ( {
- $obj = "FS::$link_table"->new( {
- $link_sourcekey => $self->$self_pkey(),
- $opt{'name_col'} => $name,
- %$link_static,
- });
-
- my @args = ();
- if ( $opt{'args_callback'} ) { #edit/process/part_event.html
- &{ $opt{'args_callback'} }( $obj,
- $prefix,
- $opt{'params'},
- \@args
- );
- }
-
- my $error = $obj->insert( @args );
- die "error inserting $obj for $link_table.$name: $error" if $error;
-
- } elsif ( $param && $obj && $opt{'args_callback'} ) {
-
- my @args = ();
- if ( $opt{'args_callback'} ) { #edit/process/part_event.html
- &{ $opt{'args_callback'} }( $obj,
- $prefix,
- $opt{'params'},
- \@args
- );
- }
-
- my $error = $obj->replace( $obj, @args );
- die "error replacing $obj for $link_table.$name: $error" if $error;
-
- }
-
- }
-
- '';
-}
-
-sub _load_table {
- my( $self, $table ) = @_;
- eval "use FS::$table";
- die $@ if $@;
- $table;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/FS/msgcat.pm b/FS/FS/msgcat.pm
deleted file mode 100644
index cbdc1d6..0000000
--- a/FS/FS/msgcat.pm
+++ /dev/null
@@ -1,133 +0,0 @@
-package FS::msgcat;
-
-use strict;
-use vars qw( @ISA );
-use Exporter;
-use FS::UID;
-use FS::Record qw( qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::msgcat - Object methods for message catalog entries
-
-=head1 SYNOPSIS
-
- use FS::msgcat;
-
- $record = new FS::msgcat \%hash;
- $record = new FS::msgcat { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::msgcat object represents an message catalog entry. FS::msgcat inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item msgnum - primary key
-
-=item msgcode - Error code
-
-=item locale - Locale
-
-=item msg - Message
-
-=back
-
-If you just want to B<use> message catalogs, see L<FS::Msgcat>.
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new message catalog entry. To add the message catalog entry to the
-database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'msgcat'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid message catalog entry. If there
-is an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('msgnum')
- || $self->ut_text('msgcode')
- || $self->ut_text('msg')
- ;
- return $error if $error;
-
- $self->locale =~ /^([\w\@]+)$/ or return "illegal locale: ". $self->locale;
- $self->locale($1);
-
- $self->SUPER::check
-}
-
-=back
-
-=head1 BUGS
-
-i18n/l10n, eek
-
-=head1 SEE ALSO
-
-L<FS::Msgcat>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/nas.pm b/FS/FS/nas.pm
deleted file mode 100644
index 97b0ea1..0000000
--- a/FS/FS/nas.pm
+++ /dev/null
@@ -1,150 +0,0 @@
-package FS::nas;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw(qsearchs); #qsearch);
-use FS::UID qw( dbh );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::nas - Object methods for nas records
-
-=head1 SYNOPSIS
-
- use FS::nas;
-
- $record = new FS::nas \%hash;
- $record = new FS::nas {
- 'nasnum' => 1,
- 'nasip' => '10.4.20.23',
- 'nasfqdn' => 'box1.brc.nv.us.example.net',
- };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->heartbeat($timestamp);
-
-=head1 DESCRIPTION
-
-An FS::nas object represents an Network Access Server on your network, such as
-a terminal server or equivalent. FS::nas inherits from FS::Record. The
-following fields are currently supported:
-
-=over 4
-
-=item nasnum - primary key
-
-=item nas - NAS name
-
-=item nasip - NAS ip address
-
-=item nasfqdn - NAS fully-qualified domain name
-
-=item last - timestamp indicating the last instant the NAS was in a known
- state (used by the session monitoring).
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new NAS. To add the NAS to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'nas'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid NAS. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('nasnum')
- || $self->ut_text('nas')
- || $self->ut_ip('nasip')
- || $self->ut_domain('nasfqdn')
- || $self->ut_numbern('last')
- || $self->SUPER::check
- ;
-}
-
-=item heartbeat TIMESTAMP
-
-Updates the timestamp for this nas
-
-=cut
-
-sub heartbeat {
- my($self, $timestamp) = @_;
- my $dbh = dbh;
- my $sth =
- $dbh->prepare("UPDATE nas SET last = ? WHERE nasnum = ? AND last < ?");
- $sth->execute($timestamp, $self->nasnum, $timestamp) or die $sth->errstr;
- $self->last($timestamp);
-}
-
-=back
-
-=head1 BUGS
-
-heartbeat method uses SQL directly and doesn't update history tables.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/option_Common.pm b/FS/FS/option_Common.pm
deleted file mode 100644
index 441e798..0000000
--- a/FS/FS/option_Common.pm
+++ /dev/null
@@ -1,345 +0,0 @@
-package FS::option_Common;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Scalar::Util qw( blessed );
-use FS::Record qw( qsearch qsearchs dbh );
-
-@ISA = qw( FS::Record );
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::option_Common - Base class for option sub-classes
-
-=head1 SYNOPSIS
-
-use FS::option_Common;
-
-@ISA = qw( FS::option_Common );
-
-#optional for non-standard names
-sub _option_table { 'table_name'; } #defaults to ${table}_option
-sub _option_namecol { 'column_name'; } #defaults to optionname
-sub _option_valuecol { 'column_name'; } #defaults to optionvalue
-
-=head1 DESCRIPTION
-
-FS::option_Common is intended as a base class for classes which have a
-simple one-to-many class associated with them, used to store a hash-like data
-structure of keys and values.
-
-=head1 METHODS
-
-=over 4
-
-=item insert [ HASHREF | OPTION => VALUE ... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-If a list or hash reference of options is supplied, option records are also
-created.
-
-=cut
-
-#false laziness w/queue.pm
-sub insert {
- my $self = shift;
- my $options =
- ( ref($_[0]) eq 'HASH' )
- ? shift
- : { @_ };
- warn "FS::option_Common::insert called on $self with options ".
- join(', ', map "$_ => ".$options->{$_}, keys %$options)
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- my $pkey = $self->primary_key;
- my $option_table = $self->option_table;
-
- my $namecol = $self->_option_namecol;
- my $valuecol = $self->_option_valuecol;
-
- foreach my $optionname ( keys %{$options} ) {
-
- my $optionvalue = $options->{$optionname};
-
- my $href = {
- $pkey => $self->get($pkey),
- $namecol => $optionname,
- $valuecol => ( ref($optionvalue) || $optionvalue ),
- };
-
- #my $option_record = eval "new FS::$option_table \$href";
- #if ( $@ ) {
- # $dbh->rollback if $oldAutoCommit;
- # return $@;
- #}
- my $option_record = "FS::$option_table"->new($href);
-
- my @args = ();
- push @args, $optionvalue if ref($optionvalue); #only hashes supported so far
-
- $error = $option_record->insert(@args);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-Delete this record from the database. Any associated option records are also
-deleted.
-
-=cut
-
-#foreign keys would make this much less tedious... grr dumb mysql
-sub delete {
- my $self = shift;
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- my $pkey = $self->primary_key;
- #my $option_table = $self->option_table;
-
- foreach my $obj ( $self->option_objects ) {
- my $error = $obj->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ... ]
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-If a list hash reference of options is supplied, option records are created or
-modified.
-
-=cut
-
-sub replace {
- my $self = shift;
-
- my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
- ? shift
- : $self->replace_old;
-
- my $options =
- ( ref($_[0]) eq 'HASH' )
- ? shift
- : { @_ };
-
- warn "FS::option_Common::replace called on $self with options ".
- join(', ', map "$_ => ". $options->{$_}, keys %$options)
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::replace($old);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- my $pkey = $self->primary_key;
- my $option_table = $self->option_table;
-
- my $namecol = $self->_option_namecol;
- my $valuecol = $self->_option_valuecol;
-
- foreach my $optionname ( keys %{$options} ) {
-
- warn "FS::option_Common::replace: inserting or replacing option: $optionname"
- if $DEBUG > 1;
-
- my $oldopt = qsearchs( $option_table, {
- $pkey => $self->get($pkey),
- $namecol => $optionname,
- } );
-
- my $optionvalue = $options->{$optionname};
-
- my %oldhash = $oldopt ? $oldopt->hash : ();
-
- my $href = {
- %oldhash,
- $pkey => $self->get($pkey),
- $namecol => $optionname,
- $valuecol => ( ref($optionvalue) || $optionvalue ),
- };
-
- #my $newopt = eval "new FS::$option_table \$href";
- #if ( $@ ) {
- # $dbh->rollback if $oldAutoCommit;
- # return $@;
- #}
- my $newopt = "FS::$option_table"->new($href);
-
- my $opt_pkey = $newopt->primary_key;
-
- $newopt->$opt_pkey($oldopt->$opt_pkey) if $oldopt;
-
- my @args = ();
- push @args, $optionvalue if ref($optionvalue); #only hashes supported so far
-
- warn "FS::option_Common::replace: ".
- ( $oldopt ? "$newopt -> replace($oldopt)" : "$newopt -> insert" )
- if $DEBUG > 2;
- my $error = $oldopt ? $newopt->replace($oldopt, @args)
- : $newopt->insert( @args);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- #remove extraneous old options
- foreach my $opt (
- grep { !exists $options->{$_->$namecol()} } $old->option_objects
- ) {
- my $error = $opt->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item option_objects
-
-Returns all options as FS::I<tablename>_option objects.
-
-=cut
-
-sub option_objects {
- my $self = shift;
- my $pkey = $self->primary_key;
- my $option_table = $self->option_table;
- qsearch($option_table, { $pkey => $self->get($pkey) } );
-}
-
-=item options
-
-Returns a list of option names and values suitable for assigning to a hash.
-
-=cut
-
-sub options {
- my $self = shift;
- my $namecol = $self->_option_namecol;
- my $valuecol = $self->_option_valuecol;
- map { $_->$namecol() => $_->$valuecol() } $self->option_objects;
-}
-
-=item option OPTIONNAME
-
-Returns the option value for the given name, or the empty string.
-
-=cut
-
-sub option {
- my $self = shift;
- my $pkey = $self->primary_key;
- my $option_table = $self->option_table;
- my $namecol = $self->_option_namecol;
- my $valuecol = $self->_option_valuecol;
- my $hashref = {
- $pkey => $self->get($pkey),
- $namecol => shift,
- };
- warn "$self -> option: searching for ".
- join(' / ', map { "$_ => ". $hashref->{$_} } keys %$hashref )
- if $DEBUG;
- my $obj = qsearchs($option_table, $hashref);
- $obj ? $obj->$valuecol() : '';
-}
-
-
-sub option_table {
- my $self = shift;
- my $option_table = $self->_option_table;
- eval "use FS::$option_table";
- die $@ if $@;
- $option_table;
-}
-
-#defaults
-sub _option_table { shift->table .'_option'; }
-sub _option_namecol { 'optionname'; }
-sub _option_valuecol { 'optionvalue'; }
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_bill_event.pm b/FS/FS/part_bill_event.pm
deleted file mode 100644
index 1d48af9..0000000
--- a/FS/FS/part_bill_event.pm
+++ /dev/null
@@ -1,363 +0,0 @@
-package FS::part_bill_event;
-
-use strict;
-use vars qw( @ISA $DEBUG @EXPORT_OK );
-use Carp qw(cluck confess);
-use FS::Record qw( dbh qsearch qsearchs );
-use FS::Conf;
-
-@ISA = qw( FS::Record );
-@EXPORT_OK = qw( due_events );
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::part_bill_event - Object methods for part_bill_event records
-
-=head1 SYNOPSIS
-
- use FS::part_bill_event;
-
- $record = new FS::part_bill_event \%hash;
- $record = new FS::part_bill_event { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->do_event( $direct_object );
-
- @events = due_events ( { 'record' => $event_triggering_record,
- 'payby' => $payby,
- 'event_time => $_date,
- 'extra_sql => $extra } );
-
-=head1 DESCRIPTION
-
-An FS::part_bill_event object represents a deprecated, old-style invoice event
-definition - a callback which is triggered when an invoice is a certain amount
-of time overdue. FS::part_bill_event inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item eventpart - primary key
-
-=item payby - CARD, DCRD, CHEK, DCHK, LECB, BILL, or COMP
-
-=item event - event name
-
-=item eventcode - event action
-
-=item seconds - how long after the invoice date events of this type are triggered
-
-=item weight - ordering for events with identical seconds
-
-=item plan - eventcode plan
-
-=item plandata - additional plan data
-
-=item reason - an associated reason for this event to fire
-
-=item disabled - Disabled flag, empty or `Y'
-
-=back
-
-=head1 NOTE
-
-Old-style invoice events are only useful for legacy migrations - if you are
-looking for current events see L<FS::part_event>.
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new invoice event definition. To add the invoice event definition to
-the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_bill_event'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid invoice event definition. If
-there is an error, returns the error, otherwise returns false. Called by the
-insert and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- $self->weight(0) unless $self->weight;
-
- my $conf = new FS::Conf;
- if ( $conf->exists('safe-part_bill_event') ) {
- my $error = $self->ut_anything('eventcode');
- return $error if $error;
-
- my $c = $self->eventcode;
-
- #yay, these regexen will go away with the event refactor
-
- $c =~ /^\s*\$cust_main\->(suspend|cancel|invoicing_list_addpost|bill|collect)\(\);\s*("";)?\s*$/
-
- or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|batch_card|send)\((%options)*\);\s*$/
-
- or $c =~ /^\s*\$cust_bill\->send(_if_newest)?\(\'[\w\-\s]+\'\s*(,\s*(\d+|\[\s*\d+(,\s*\d+)*\s*\])\s*,\s*'[\w\@\.\-\+]*'\s*)?\);\s*$/
-
-# or $c =~ /^\s*\$cust_main\->apply_payments; \$cust_main->apply_credits; "";\s*$/
- or $c =~ /^\s*\$cust_main\->apply_payments_and_credits; "";\s*$/
-
- or $c =~ /^\s*\$cust_main\->charge\( \s*\d*\.?\d*\s*,\s*\'[\w \!\@\#\$\%\&\(\)\-\+\;\:\"\,\.\?\/]*\'\s*\);\s*$/
-
- or $c =~ /^\s*\$cust_main\->suspend_(if|unless)_pkgpart\([\d\,\s]*\);\s*$/
-
- or $c =~ /^\s*\$cust_bill\->cust_suspend_if_balance_over\([\d\.\s]*\);\s*$/
-
- or do {
- #log
- return "illegal eventcode: $c";
- };
-
- }
-
- my $error = $self->ut_numbern('eventpart')
- || $self->ut_enum('payby', [qw( CARD DCLN DCRD CHEK DCHK LECB BILL COMP )] )
- || $self->ut_text('event')
- || $self->ut_anything('eventcode')
- || $self->ut_number('seconds')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- || $self->ut_number('weight')
- || $self->ut_textn('plan')
- || $self->ut_anything('plandata')
- || $self->ut_numbern('reason')
- ;
- #|| $self->ut_snumber('seconds')
- return $error if $error;
-
- #quelle kludge
- if ( $self->plandata =~ /^(agent_)?templatename\s+(.*)$/m ) {
- my $name= $2;
-
- foreach my $file (qw( template
- latex latexnotes latexreturnaddress latexfooter
- latexsmallfooter
- html htmlnotes htmlreturnaddress htmlfooter
- ))
- {
- unless ( $conf->exists("invoice_${file}_$name") ) {
- $conf->set(
- "invoice_${file}_$name" =>
- join("\n", $conf->config("invoice_$file") )
- );
- }
- }
- }
-
- if ($self->reason){
- my $reasonr = qsearchs('reason', {'reasonnum' => $self->reason});
- return "Unknown reason" unless $reasonr;
- }
-
- $self->SUPER::check;
-}
-
-=item templatename
-
-Returns the alternate invoice template name, if any, or false if there is
-no alternate template for this invoice event.
-
-=cut
-
-sub templatename {
- my $self = shift;
- if ( $self->plan =~ /^send_(alternate|agent)$/
- && $self->plandata =~ /^(agent_)?templatename (.*)$/m
- )
- {
- $2;
- } else {
- '';
- }
-}
-
-=item due_events
-
-Returns the list of events due, if any, or false if there is none.
-Requires record and payby, but event_time and extra_sql are optional.
-
-=cut
-
-sub due_events {
- my ($record, $payby, $event_time, $extra_sql) = @_;
-
- #cluck "DEPRECATED: FS::part_bill_event::due_events called on $record";
- confess "DEPRECATED: FS::part_bill_event::due_events called on $record";
-
- my $interval = 0;
- if ($record->_date){
- $event_time = time unless $event_time;
- $interval = $event_time - $record->_date;
- }
- sort { $a->seconds <=> $b->seconds
- || $a->weight <=> $b->weight
- || $a->eventpart <=> $b->eventpart }
- grep { $_->seconds <= ( $interval )
- && ! qsearch( 'cust_bill_event', {
- 'invnum' => $record->get($record->dbdef_table->primary_key),
- 'eventpart' => $_->eventpart,
- 'status' => 'done',
- } )
- }
- qsearch( {
- 'table' => 'part_bill_event',
- 'hashref' => { 'payby' => $payby,
- 'disabled' => '', },
- 'extra_sql' => $extra_sql,
- } );
-
-
-}
-
-=item do_event
-
-Performs the event and returns any errors that occur.
-Requires a record on which to perform the event.
-Should only be performed inside a transaction.
-
-=cut
-
-sub do_event {
- my ($self, $object, %options) = @_;
-
- #cluck "DEPRECATED: FS::part_bill_event::do_event called on $self";
- confess "DEPRECATED: FS::part_bill_event::do_event called on $self";
-
- warn " calling event (". $self->eventcode. ") for " . $object->table . " " ,
- $object->get($object->dbdef_table->primary_key) . "\n" if $DEBUG > 1;
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
-
- # for "callback" -- heh
- my $cust_main = $object->cust_main;
- my $cust_bill;
- if ($object->table eq 'cust_bill'){
- $cust_bill = $object;
- }
- my $cust_pay_batch;
- if ($object->table eq 'cust_pay_batch'){
- $cust_pay_batch = $object;
- }
-
- my $error;
- {
- local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
- $error = eval $self->eventcode;
- }
-
- my $status = '';
- my $statustext = '';
- if ( $@ ) {
- $status = 'failed';
- $statustext = $@;
- } elsif ( $error ) {
- $status = 'done';
- $statustext = $error;
- } else {
- $status = 'done';
- }
-
- #add cust_bill_event
- my $cust_bill_event = new FS::cust_bill_event {
-# 'invnum' => $object->get($object->dbdef_table->primary_key),
- 'invnum' => $object->invnum,
- 'eventpart' => $self->eventpart,
- '_date' => time,
- 'status' => $status,
- 'statustext' => $statustext,
- };
- $error = $cust_bill_event->insert;
- if ( $error ) {
- my $e = 'WARNING: Event run but database not updated - '.
- 'error inserting cust_bill_event, invnum #'. $object->invnum .
- ', eventpart '. $self->eventpart.": $error";
- warn $e;
- return $e;
- }
- '';
-}
-
-=item reasontext
-
-Returns the text of any reason associated with this event.
-
-=cut
-
-sub reasontext {
- my $self = shift;
- my $r = qsearchs('reason', { 'reasonnum' => $self->reason });
- if ($r){
- $r->reason;
- }else{
- '';
- }
-}
-
-=back
-
-=head1 BUGS
-
-The whole "eventcode" idea is bunk. This should be refactored with subclasses
-like part_pkg/ and part_export/
-
-=head1 SEE ALSO
-
-L<FS::cust_bill>, L<FS::cust_bill_event>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_event.pm b/FS/FS/part_event.pm
deleted file mode 100644
index d0ab65e..0000000
--- a/FS/FS/part_event.pm
+++ /dev/null
@@ -1,428 +0,0 @@
-package FS::part_event;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Carp qw(confess);
-use FS::Record qw( dbh qsearch qsearchs );
-use FS::option_Common;
-use FS::m2name_Common;
-use FS::Conf;
-use FS::part_event_option;
-use FS::part_event_condition;
-use FS::cust_event;
-use FS::agent;
-
-@ISA = qw( FS::m2name_Common FS::option_Common ); # FS::Record );
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::part_event - Object methods for part_event records
-
-=head1 SYNOPSIS
-
- use FS::part_event;
-
- $record = new FS::part_event \%hash;
- $record = new FS::part_event { 'column' => 'value' };
-
- $error = $record->insert( { 'option' => 'value' } );
- $error = $record->insert( \%options );
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->do_event( $direct_object );
-
-=head1 DESCRIPTION
-
-An FS::part_event object represents an event definition - a billing, collection
-or other callback which is triggered when certain customer, invoice, package or
-other conditions are met. FS::part_event inherits from FS::Record. The
-following fields are currently supported:
-
-=over 4
-
-=item eventpart - primary key
-
-=item agentnum - Optional agentnum (see L<FS::agent>)
-
-=item event - event name
-
-=item eventtable - table name against which this event is triggered; currently "cust_bill" (the traditional invoice events), "cust_main" (customer events) or "cust_pkg (package events)
-
-=item check_freq - how often events of this type are checked; currently "1d" (daily) and "1m" (monthly) are recognized. Note that the apprioriate freeside-daily and/or freeside-monthly cron job needs to be in place.
-
-=item weight - ordering for events
-
-=item action - event action (like part_bill_event.plan - eventcode plan)
-
-=item disabled - Disabled flag, empty or `Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new invoice event definition. To add the invoice event definition to
-the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_event'; }
-
-=item insert [ HASHREF ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-If a list or hash reference of options is supplied, part_export_option records
-are created (see L<FS::part_event_option>).
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD [ HASHREF | OPTION => VALUE ... ]
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-If a list or hash reference of options is supplied, part_event_option
-records are created or modified (see L<FS::part_event_option>).
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid invoice event definition. If
-there is an error, returns the error, otherwise returns false. Called by the
-insert and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- $self->weight(0) unless $self->weight;
-
- my $error =
- $self->ut_numbern('eventpart')
- || $self->ut_text('event')
- || $self->ut_enum('eventtable', [ 'cust_bill', 'cust_main', 'cust_pkg' ] )
- || $self->ut_enum('check_freq', [ '1d', '1m' ])
- || $self->ut_number('weight')
- || $self->ut_alpha('action')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- || $self->ut_agentnum_acl('agentnum', 'Edit global billing events')
- ;
- return $error if $error;
-
- #XXX check action to make sure a module exists?
- # well it'll die in _rebless...
-
- $self->SUPER::check;
-}
-
-=item _rebless
-
-Reblesses the object into the FS::part_event::Action::ACTION class, where
-ACTION is the object's I<action> field.
-
-=cut
-
-sub _rebless {
- my $self = shift;
- my $action = $self->action or return $self;
- #my $class = ref($self). "::$action";
- my $class = "FS::part_event::Action::$action";
- eval "use $class";
- die $@ if $@;
- bless($self, $class); # unless $@;
- $self;
-}
-
-=item part_event_condition
-
-Returns the conditions associated with this event, as FS::part_event_condition
-objects (see L<FS::part_event_condition>)
-
-=cut
-
-sub part_event_condition {
- my $self = shift;
- qsearch( 'part_event_condition', { 'eventpart' => $self->eventpart } );
-}
-
-=item new_cust_event OBJECT
-
-Creates a new customer event (see L<FS::cust_event>) for the provided object.
-
-=cut
-
-sub new_cust_event {
- my( $self, $object ) = @_;
-
- confess "**** $object is not a ". $self->eventtable
- if ref($object) ne "FS::". $self->eventtable;
-
- my $pkey = $object->primary_key;
-
- new FS::cust_event {
- 'eventpart' => $self->eventpart,
- 'tablenum' => $object->$pkey(),
- '_date' => time, #i think we always want the real "now" here.
- 'status' => 'new',
- };
-}
-
-#surely this doesn't work
-sub reasontext { confess "part_event->reasontext deprecated"; }
-#=item reasontext
-#
-#Returns the text of any reason associated with this event.
-#
-#=cut
-#
-#sub reasontext {
-# my $self = shift;
-# my $r = qsearchs('reason', { 'reasonnum' => $self->reason });
-# if ($r){
-# $r->reason;
-# }else{
-# '';
-# }
-#}
-
-=item agent
-
-Returns the associated agent for this event, if any, as an FS::agent object.
-
-=cut
-
-sub agent {
- my $self = shift;
- qsearchs('agent', { 'agentnum' => $self->agentnum } );
-}
-
-=item templatename
-
-Returns the alternate invoice template name, if any, or false if there is
-no alternate template for this event.
-
-=cut
-
-sub templatename {
-
- my $self = shift;
- if ( $self->action =~ /^cust_bill_send_(alternate|agent)$/
- && ( $self->option('agent_templatename')
- || $self->option('templatename') )
- )
- {
- $self->option('agent_templatename')
- || $self->option('templatename');
-
- } else {
- '';
- }
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item eventtable_labels
-
-Returns a hash reference of labels for eventtable values,
-i.e. 'cust_main'=>'Customer'
-
-=cut
-
-sub eventtable_labels {
- #my $class = shift;
-
- tie my %hash, 'Tie::IxHash',
- 'cust_pkg' => 'Package',
- 'cust_bill' => 'Invoice',
- 'cust_main' => 'Customer',
- 'cust_pay_batch' => 'Batch payment',
- ;
-
- \%hash
-}
-
-=item eventtable_pkey_sql
-
-Returns a hash reference of full SQL primary key names for eventtable values,
-i.e. 'cust_main'=>'cust_main.custnum'
-
-=cut
-
-sub eventtable_pkey_sql {
- #my $class = shift;
-
- my %hash = (
- 'cust_main' => 'cust_main.custnum',
- 'cust_bill' => 'cust_bill.invnum',
- 'cust_pkg' => 'cust_pkg.pkgnum',
- 'cust_pay_batch' => 'cust_pay_batch.paybatchnum',
- );
-
- \%hash;
-}
-
-
-=item eventtables
-
-Returns a list of eventtable values (default ordering; suited for display).
-
-=cut
-
-sub eventtables {
- my $class = shift;
- my $eventtables = $class->eventtable_labels;
- keys %$eventtables;
-}
-
-=item eventtables_runorder
-
-Returns a list of eventtable values (run order).
-
-=cut
-
-sub eventtables_runorder {
- shift->eventtables; #same for now
-}
-
-=item check_freq_labels
-
-Returns a hash reference of labels for check_freq values,
-i.e. '1d'=>'daily'
-
-=cut
-
-sub check_freq_labels {
- #my $class = shift;
-
- #Tie::IxHash??
- {
- '1d' => 'daily',
- '1m' => 'monthly',
- };
-}
-
-=item actions [ EVENTTABLE ]
-
-Return information about the available actions. If an eventtable is specified,
-only return information about actions available for that eventtable.
-
-Information is returned as key-value pairs. Keys are event names. Values are
-hashrefs with the following keys:
-
-=over 4
-
-=item description
-
-=item eventtable_hashref
-
-=item option_fields
-
-=item default_weight
-
-=item deprecated
-
-=back
-
-See L<FS::part_event::Action> for more information.
-
-=cut
-
-#false laziness w/part_event_condition.pm
-#some false laziness w/part_export & part_pkg
-my %actions;
-foreach my $INC ( @INC ) {
- foreach my $file ( glob("$INC/FS/part_event/Action/*.pm") ) {
- warn "attempting to load Action from $file\n" if $DEBUG;
- $file =~ /\/(\w+)\.pm$/ or do {
- warn "unrecognized file in $INC/FS/part_event/Action/: $file\n";
- next;
- };
- my $mod = $1;
- eval "use FS::part_event::Action::$mod;";
- if ( $@ ) {
- die "error using FS::part_event::Action::$mod (skipping): $@\n" if $@;
- #warn "error using FS::part_event::Action::$mod (skipping): $@\n" if $@;
- #next;
- }
- $actions{$mod} = {
- ( map { $_ => "FS::part_event::Action::$mod"->$_() }
- qw( description eventtable_hashref default_weight deprecated )
- #option_fields_hashref
- ),
- 'option_fields' => [ "FS::part_event::Action::$mod"->option_fields() ],
- };
- }
-}
-
-sub actions {
- my( $class, $eventtable ) = @_;
- (
- map { $_ => $actions{$_} }
- sort { $actions{$a}->{'default_weight'}<=>$actions{$b}->{'default_weight'} }
- $class->all_actions( $eventtable )
- );
-
-}
-
-=item all_actions [ EVENTTABLE ]
-
-Returns a list of just the action names
-
-=cut
-
-sub all_actions {
- my ( $class, $eventtable ) = @_;
-
- grep { !$eventtable || $actions{$_}->{'eventtable_hashref'}{$eventtable} }
- keys %actions
-}
-
-=back
-
-=head1 SEE ALSO
-
-L<FS::part_event_option>, L<FS::part_event_condition>, L<FS::cust_main>,
-L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_bill_event>, L<FS::Record>,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_event/Action.pm b/FS/FS/part_event/Action.pm
deleted file mode 100644
index bdb9df6..0000000
--- a/FS/FS/part_event/Action.pm
+++ /dev/null
@@ -1,224 +0,0 @@
-package FS::part_event::Action;
-
-use strict;
-use base qw( FS::part_event );
-use Tie::IxHash;
-
-=head1 NAME
-
-FS::part_event::Action - Base class for event actions
-
-=head1 SYNOPSIS
-
-package FS::part_event::Action::myaction;
-
-use base FS::part_event::Action;
-
-=head1 DESCRIPTION
-
-FS::part_event::Action is a base class for event action classes.
-
-=head1 METHODS
-
-These methods are implemented in each action class.
-
-=over 4
-
-=item description
-
-Action classes must define a description method. This method should return a
-scalar description of the action.
-
-=item eventtable_hashref
-
-Action classes must define a eventtable_hashref method if they can only be
-triggered against some kinds of tables. This method should return a hash
-reference of eventtables (values set true indicate the action can be performed):
-
- sub eventtable_hashref {
- { 'cust_main' => 1,
- 'cust_bill' => 1,
- 'cust_pkg' => 0,
- 'cust_pay_batch' => 0,
- };
- }
-
-=cut
-
-#fallback
-sub eventtable_hashref {
- { 'cust_main' => 1,
- 'cust_bill' => 1,
- 'cust_pkg' => 1,
- 'cust_pay_batch' => 1,
- };
-}
-
-=item option_fields
-
-Action classes may define an option_fields method to indicate that they
-accept one or more options.
-
-This method should return a list of option names and option descriptions.
-Each option description can be a scalar description, for simple options, or a
-hashref with the following values:
-
-=item label - Description
-
-=item type - Currently text, money, checkbox, checkbox-multiple, select, select-agent, select-pkg_class, select-part_referral, select-table, fixed, hidden, (others can be implemented as httemplate/elements/tr-TYPE.html mason components). Defaults to text.
-
-=item size - Size for text fields
-
-=item options - For checkbox-multiple and select, a list reference of available option values.
-
-=item option_labels - For select, a hash reference of availble option values and labels.
-
-=item value - for checkbox, fixed, hidden
-
-=item table - for select-table
-
-=item name_col - for select-table
-
-=item NOTE: See httemplate/elements/select-table.html for a full list of the optinal options for the select-table type
-
-=back
-
-NOTE: A database connection is B<not> yet available when this subroutine is
-executed.
-
-Example:
-
- sub option_fields {
- (
- 'field' => 'description',
-
- 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
-
- 'third_field' => { 'label' => 'Types',
- 'type' => 'select',
- 'options' => [ 'h', 's' ],
- 'option_labels' => { 'h' => 'Happy',
- 's' => 'Sad',
- },
- );
- }
-
-=cut
-
-#fallback
-sub option_fields {
- ();
-}
-
-=item default_weight
-
-Action classes may define a default weighting. Weights control execution order
-relative to other actions (that are triggered at the same time).
-
-=cut
-
-#fallback
-sub default_weight {
- 100;
-}
-
-=item deprecated
-
-Action classes may define a deprecated method that returns true, indicating
-that this action is deprecated.
-
-=cut
-
-#default
-sub deprecated {
- 0;
-}
-
-=item do_action CUSTOMER_EVENT_OBJECT
-
-Action classes must define an action method. This method is triggered if
-all conditions have been met.
-
-The object which triggered the event (an FS::cust_main, FS::cust_bill or
-FS::cust_pkg object) is passed as an argument.
-
-To retreive option values, call the option method on the desired option, i.e.:
-
- my( $self, $cust_object ) = @_;
- $value_of_field = $self->option('field');
-
-To indicate sucessful completion, simply return. Optionally, you can return a
-string of information status information about the sucessful completion, or
-simply return the empty string.
-
-To indicate a failure and that this event should retry, die with the desired
-error message.
-
-=back
-
-=head1 BASE METHODS
-
-These methods are defined in the base class for use in action classes.
-
-=over 4
-
-=item cust_main CUST_OBJECT
-
-Return the customer object (see L<FS::cust_main>) associated with the provided
-object (the object itself if it is already a customer object).
-
-=cut
-
-sub cust_main {
- my( $self, $cust_object ) = @_;
-
- $cust_object->isa('FS::cust_main') ? $cust_object : $cust_object->cust_main;
-
-}
-
-=item option_label OPTIONNAME
-
-Returns the label for the specified option name.
-
-=cut
-
-sub option_label {
- my( $self, $optionname ) = @_;
-
- my %option_fields = $self->option_fields;
-
- ref( $option_fields{$optionname} )
- ? $option_fields{$optionname}->{'label'}
- : $option_fields{$optionname}
- or $optionname;
-}
-
-=item option_fields_hashref
-
-Returns the option fields as an (ordered) hash reference.
-
-=cut
-
-sub option_fields_hashref {
- my $self = shift;
- tie my %hash, 'Tie::IxHash', $self->option_fields;
-}
-
-=item option_fields_listref
-
-Returns just the option field names as a list reference.
-
-=cut
-
-sub option_fields_listref {
- my $self = shift;
- my $hashref = $self->option_fields_hashref;
- [ keys %$hashref ];
-}
-
-=back
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_event/Action/addpost.pm b/FS/FS/part_event/Action/addpost.pm
deleted file mode 100644
index e0e3fa8..0000000
--- a/FS/FS/part_event/Action/addpost.pm
+++ /dev/null
@@ -1,24 +0,0 @@
-package FS::part_event::Action::addpost;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Add postal invoicing';
-}
-
-sub default_weight {
- 20;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- $cust_main->invoicing_list_addpost();
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/apply.pm b/FS/FS/part_event/Action/apply.pm
deleted file mode 100644
index f91c604..0000000
--- a/FS/FS/part_event/Action/apply.pm
+++ /dev/null
@@ -1,28 +0,0 @@
-package FS::part_event::Action::apply;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Apply unapplied payments and credits';
-}
-
-sub deprecated {
- 1;
-}
-
-sub default_weight {
- 70;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- $cust_main->apply_payments_and_credits;
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/bill.pm b/FS/FS/part_event/Action/bill.pm
deleted file mode 100644
index fec025f..0000000
--- a/FS/FS/part_event/Action/bill.pm
+++ /dev/null
@@ -1,30 +0,0 @@
-package FS::part_event::Action::bill;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- #'Generate invoices (normally only used with a <i>Late Fee</i> event)';
- 'Generate invoices (normally only used with a Late Fee event)';
-}
-
-sub deprecated {
- 1;
-}
-
-sub default_weight {
- 60;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- my $error = $cust_main->bill;
- die $error if $error;
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cancel.pm b/FS/FS/part_event/Action/cancel.pm
deleted file mode 100644
index 94f3146..0000000
--- a/FS/FS/part_event/Action/cancel.pm
+++ /dev/null
@@ -1,35 +0,0 @@
-package FS::part_event::Action::cancel;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Cancel';
-}
-
-sub option_fields {
- (
- 'reasonnum' => { 'label' => 'Reason',
- 'type' => 'select-reason',
- 'reason_class' => 'C',
- },
- );
-
-};
-
-sub default_weight {
- 20;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- my $error = $cust_main->cancel( 'reason' => $self->option('reasonnum') );
- die $error if $error;
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/collect.pm b/FS/FS/part_event/Action/collect.pm
deleted file mode 100644
index fa94b7d..0000000
--- a/FS/FS/part_event/Action/collect.pm
+++ /dev/null
@@ -1,30 +0,0 @@
-package FS::part_event::Action::collect;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- #'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)';
- 'Collect on invoices (normally only used with a Late Fee and Generate Invoice events)';
-}
-
-sub deprecated {
- 1;
-}
-
-sub default_weight {
- 80;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- my $error = $cust_main->collect;
- die $error if $error;
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_batch.pm b/FS/FS/part_event/Action/cust_bill_batch.pm
deleted file mode 100644
index aec0925..0000000
--- a/FS/FS/part_event/Action/cust_bill_batch.pm
+++ /dev/null
@@ -1,31 +0,0 @@
-package FS::part_event::Action::cust_bill_batch;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Add card or check to a pending batch';
-}
-
-sub deprecated {
- 1;
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub default_weight {
- 40;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->batch_card; # ( %options ); #XXX options??
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_comp.pm b/FS/FS/part_event/Action/cust_bill_comp.pm
deleted file mode 100644
index 636a66d..0000000
--- a/FS/FS/part_event/Action/cust_bill_comp.pm
+++ /dev/null
@@ -1,34 +0,0 @@
-package FS::part_event::Action::cust_bill_comp;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Pay invoice with a complimentary "payment"';
-}
-
-sub deprecated {
- 1;
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub default_weight {
- 30;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- my $error = $cust_bill->comp;
- die $error if $error;
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_fee_percent.pm b/FS/FS/part_event/Action/cust_bill_fee_percent.pm
deleted file mode 100644
index 100fc8b..0000000
--- a/FS/FS/part_event/Action/cust_bill_fee_percent.pm
+++ /dev/null
@@ -1,40 +0,0 @@
-package FS::part_event::Action::cust_bill_fee_percent;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Late fee (percentage of invoice)';
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub option_fields {
- (
- 'percent' => { label=>'Percent', size=>2, },
- 'reason' => 'Reason',
- );
-}
-
-sub default_weight {
- 10;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- my $error = $cust_main->charge(
- sprintf('%.2f', $cust_bill->owed * $self->option('percent') / 100 ),
- $self->option('reason')
- );
- die $error if $error;
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_realtime_card.pm b/FS/FS/part_event/Action/cust_bill_realtime_card.pm
deleted file mode 100644
index 471c946..0000000
--- a/FS/FS/part_event/Action/cust_bill_realtime_card.pm
+++ /dev/null
@@ -1,32 +0,0 @@
-package FS::part_event::Action::cust_bill_realtime_card;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- #'Run card with a <a href="http://420.am/business-onlinepayment/">Business::OnlinePayment</a> realtime gateway';
- 'Run card with a Business::OnlinePayment realtime gateway';
-}
-
-sub deprecated {
- 1;
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub default_weight {
- 30;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->realtime_card;
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_realtime_check.pm b/FS/FS/part_event/Action/cust_bill_realtime_check.pm
deleted file mode 100644
index 9a52830..0000000
--- a/FS/FS/part_event/Action/cust_bill_realtime_check.pm
+++ /dev/null
@@ -1,32 +0,0 @@
-package FS::part_event::Action::cust_bill_realtime_check;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- #'Run check with a <a href="http://420.am/business-onlinepayment/">Business::OnlinePayment</a> realtime gateway';
- 'Run check with a Business::OnlinePayment realtime gateway';
-}
-
-sub deprecated {
- 1;
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub default_weight {
- 30;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->realtime_ach;
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm b/FS/FS/part_event/Action/cust_bill_realtime_lec.pm
deleted file mode 100644
index db091da..0000000
--- a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm
+++ /dev/null
@@ -1,32 +0,0 @@
-package FS::part_event::Action::cust_bill_realtime_lec;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- #'Run phone bill ("LEC") billing with a <a href="http://420.am/business-onlinepayment/">Business::OnlinePayment</a> realtime gateway';
- 'Run phone bill ("LEC") billing with a Business::OnlinePayment realtime gateway';
-}
-
-sub deprecated {
- 1;
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub default_weight {
- 30;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->realtime_lec;
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_send.pm b/FS/FS/part_event/Action/cust_bill_send.pm
deleted file mode 100644
index 9330c61..0000000
--- a/FS/FS/part_event/Action/cust_bill_send.pm
+++ /dev/null
@@ -1,27 +0,0 @@
-package FS::part_event::Action::cust_bill_send;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Send invoice (email/print/fax)';
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub default_weight {
- 50;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->send;
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_agent.pm b/FS/FS/part_event/Action/cust_bill_send_agent.pm
deleted file mode 100644
index fcf0007..0000000
--- a/FS/FS/part_event/Action/cust_bill_send_agent.pm
+++ /dev/null
@@ -1,44 +0,0 @@
-package FS::part_event::Action::cust_bill_send_agent;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Send invoice (email/print/fax) with alternate template, for specific agents';
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub option_fields {
- (
- 'agentnum' => { label => 'Only for agent(s)',
- type => 'select-agent',
- multiple => 1
- },
- 'agent_templatename' => { label => 'Template',
- type => 'select-invoice_template',
- },
- 'agent_invoice_from' => 'Invoice email From: address',
- );
-}
-
-sub default_weight {
- 50;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->send(
- $self->option('agent_templatename'),
- [ split(/\s*,\s*/, $self->option('agentnum') ) ],
- $self->option('agent_invoice_from'),
- );
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_alternate.pm b/FS/FS/part_event/Action/cust_bill_send_alternate.pm
deleted file mode 100644
index 6afb89a..0000000
--- a/FS/FS/part_event/Action/cust_bill_send_alternate.pm
+++ /dev/null
@@ -1,35 +0,0 @@
-package FS::part_event::Action::cust_bill_send_alternate;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Send invoice (email/print/fax) with alternate template';
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub option_fields {
- (
- 'templatename' => { label => 'Template',
- type => 'select-invoice_template',
- },
- );
-}
-
-sub default_weight {
- 50;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->send( $self->option('templatename') );
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm
deleted file mode 100644
index db3554e..0000000
--- a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm
+++ /dev/null
@@ -1,56 +0,0 @@
-package FS::part_event::Action::cust_bill_send_csv_ftp;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Upload CSV invoice data to an FTP server';
-}
-
-sub deprecated {
- 1;
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub option_fields {
- (
- 'ftpformat' => { label => 'Format',
- type =>'select',
- options => ['default', 'billco'],
- option_labels => { 'default' => 'Default',
- 'billco' => 'Billco',
- },
- },
- 'ftpserver' => 'FTP server',
- 'ftpusername' => 'FTP username',
- 'ftppassword' => 'FTP password',
- 'ftpdir' => 'FTP directory',
- );
-}
-
-sub default_weight {
- 50;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->send_csv(
- 'protocol' => 'ftp',
- 'server' => $self->option('ftpserver'),
- 'username' => $self->option('ftpusername'),
- 'password' => $self->option('ftppassword'),
- 'dir' => $self->option('ftpdir'),
- 'format' => $self->option('ftpformat'),
- );
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_if_newest.pm b/FS/FS/part_event/Action/cust_bill_send_if_newest.pm
deleted file mode 100644
index 916983e..0000000
--- a/FS/FS/part_event/Action/cust_bill_send_if_newest.pm
+++ /dev/null
@@ -1,40 +0,0 @@
-package FS::part_event::Action::cust_bill_send_if_newest;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Send invoice (email/print/fax) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)';
-}
-
-# XXX is this handled better by something against customers??
-#sub deprecated {
-# 1;
-#}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub option_fields {
- (
- 'if_newest_templatename' => { label => 'Template',
- type => 'select-invoice_template',
- },
- );
-}
-
-sub default_weight {
- 50;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->send( $self->option('templatename') );
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_spool_csv.pm b/FS/FS/part_event/Action/cust_bill_spool_csv.pm
deleted file mode 100644
index 4300b61..0000000
--- a/FS/FS/part_event/Action/cust_bill_spool_csv.pm
+++ /dev/null
@@ -1,64 +0,0 @@
-package FS::part_event::Action::cust_bill_spool_csv;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Spool CSV invoice data';
-}
-
-sub deprecated {
- 1;
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub option_fields {
- (
- 'spoolformat' => { label => 'Format',
- type => 'select',
- options => ['default', 'billco'],
- option_labels => { 'default' => 'Default',
- 'billco' => 'Billco',
- },
- },
- 'spooldest' => { label => 'For destination',
- type => 'select',
- options => [ '', qw( POST EMAIL FAX ) ],
- option_labels => { '' => '(all)',
- 'POST' => 'Postal Mail',
- 'EMAIL' => 'Email',
- 'FAX' => 'Fax',
- },
- },
- 'spoolbalanceover' => { label =>
- 'If balance (this invoice and previous) over',
- type => 'money',
- },
- 'spoolagent_spools' => { label => 'Individual per-agent spools',
- type => 'checkbox',
- },
- );
-}
-
-sub default_weight {
- 50;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->spool_csv(
- 'format' => $self->option('spoolformat'),
- 'dest' => $self->option('spooldest'),
- 'balanceover' => $self->option('spoolbalanceover'),
- 'agent_spools' => $self->option('spoolagent_spools'),
- );
-}
-
-1;
diff --git a/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm b/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm
deleted file mode 100644
index 6559949..0000000
--- a/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm
+++ /dev/null
@@ -1,48 +0,0 @@
-package FS::part_event::Action::cust_bill_suspend_if_balance;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Suspend if balance (this invoice and previous) over';
-}
-
-sub deprecated {
- 1;
-}
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub option_fields {
- (
- 'balanceover' => { label=>'Balance over', type=>'money', }, # size=>7 },
- 'reasonnum' => { 'label' => 'Reason',
- 'type' => 'select-reason',
- 'reason_class' => 'S',
- },
- );
-};
-
-sub default_weight {
- 10;
-}
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- my @err = $cust_bill->cust_suspend_if_balance_over(
- $self->option('balanceover'),
- 'reason' => $self->option('reasonnum'),
- );
-
- die join(' / ', @err) if scalar(@err);
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/fee.pm b/FS/FS/part_event/Action/fee.pm
deleted file mode 100644
index 81a8449..0000000
--- a/FS/FS/part_event/Action/fee.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::part_event::Action::fee;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Late fee (flat)';
-}
-
-sub option_fields {
- (
- 'charge' => { label=>'Amount', type=>'money', }, # size=>7, },
- 'reason' => 'Reason',
- );
-};
-
-sub default_weight {
- 10;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- my $error = $cust_main->charge( $self->option('charge'), $self->option('reason') );
-
- die $error if $error;
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/suspend.pm b/FS/FS/part_event/Action/suspend.pm
deleted file mode 100644
index ec440ff..0000000
--- a/FS/FS/part_event/Action/suspend.pm
+++ /dev/null
@@ -1,36 +0,0 @@
-package FS::part_event::Action::suspend;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Suspend';
-}
-
-sub option_fields {
- (
- 'reasonnum' => { 'label' => 'Reason',
- 'type' => 'select-reason',
- 'reason_class' => 'S',
- },
- );
-};
-
-sub default_weight {
- 10;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- my @err = $cust_main->suspend( 'reason' => $self->option('reasonnum') );
-
- die join(' / ', @err) if scalar(@err);
-
- '';
-
-}
-
-1;
diff --git a/FS/FS/part_event/Action/suspend_if_pkgpart.pm b/FS/FS/part_event/Action/suspend_if_pkgpart.pm
deleted file mode 100644
index 9bdc9be..0000000
--- a/FS/FS/part_event/Action/suspend_if_pkgpart.pm
+++ /dev/null
@@ -1,42 +0,0 @@
-package FS::part_event::Action::suspend_if_pkgpart;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Suspend packages';
-}
-
-sub option_fields {
- (
- 'if_pkgpart' => { 'label' => 'Suspend packages:',
- 'type' => 'select-part_pkg',
- 'multiple' => 1,
- },
- 'reasonnum' => { 'label' => 'Reason',
- 'type' => 'select-reason',
- 'reason_class' => 'S',
- },
- );
-};
-
-sub default_weight {
- 10;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- my @err = $cust_main->suspend_if_pkgpart( {
- 'pkgparts' => [ split(/\s*,\s*/, $self->option('if_pkgpart') ) ],
- 'reason' => $self->option('reasonnum'),
- } );
-
- die join(' / ', @err) if scalar(@err);
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Action/suspend_unless_pkgpart.pm b/FS/FS/part_event/Action/suspend_unless_pkgpart.pm
deleted file mode 100644
index f9bf1e8..0000000
--- a/FS/FS/part_event/Action/suspend_unless_pkgpart.pm
+++ /dev/null
@@ -1,42 +0,0 @@
-package FS::part_event::Action::suspend_unless_pkgpart;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- 'Suspend packages except';
-}
-
-sub option_fields {
- (
- 'unless_pkgpart' => { 'label' => 'Suspend packages except:',
- 'type' => 'select-part_pkg',
- 'multiple' => 1,
- },
- 'reasonnum' => { 'label' => 'Reason',
- 'type' => 'select-reason',
- 'reason_class' => 'S',
- },
- );
-};
-
-sub default_weight {
- 10;
-}
-
-sub do_action {
- my( $self, $cust_object ) = @_;
-
- my $cust_main = $self->cust_main($cust_object);
-
- my @err = $cust_main->suspend_unless_pkgpart( {
- 'pkgparts' => [ split(/\s*,\s*/, $self->option('unless_pkgpart') ) ],
- 'reason' => $self->option('reasonnum'),
- } );
-
- die join(' / ', @err) if scalar(@err);
-
- '';
-}
-
-1;
diff --git a/FS/FS/part_event/Condition.pm b/FS/FS/part_event/Condition.pm
deleted file mode 100644
index 2b71fbb..0000000
--- a/FS/FS/part_event/Condition.pm
+++ /dev/null
@@ -1,412 +0,0 @@
-package FS::part_event::Condition;
-
-use strict;
-use base qw( FS::part_event_condition );
-
-use FS::UID qw( driver_name );
-
-=head1 NAME
-
-FS::part_event::Condition - Base class for event conditions
-
-=head1 SYNOPSIS
-
-package FS::part_event::Condition::mycondition;
-
-use base FS::part_event::Condition;
-
-=head1 DESCRIPTION
-
-FS::part_event::Condition is a base class for event conditions classes.
-
-=head1 METHODS
-
-These methods are implemented in each condition class.
-
-=over 4
-
-=item description
-
-Condition classes must define a description method. This method should return
-a scalar description of the condition.
-
-=item eventtable_hashref
-
-Condition classes must define an eventtable_hashref method if they can only be
-tested against some kinds of tables. This method should return a hash reference
-of eventtables (values set true indicate the condition can be tested):
-
- sub eventtable_hashref {
- { 'cust_main' => 1,
- 'cust_bill' => 1,
- 'cust_pkg' => 0,
- 'cust_pay_batch' => 0,
- };
- }
-
-=cut
-
-#fallback
-sub eventtable_hashref {
- { 'cust_main' => 1,
- 'cust_bill' => 1,
- 'cust_pkg' => 1,
- 'cust_pay_batch' => 1,
- };
-}
-
-=item option_fields
-
-Condition classes may define an option_fields method to indicate that they
-accept one or more options.
-
-This method should return a list of option names and option descriptions.
-Each option description can be a scalar description, for simple options, or a
-hashref with the following values:
-
-=over 4
-
-=item label - Description
-
-=item type - Currently text, money, checkbox, checkbox-multiple, select, select-agent, select-pkg_class, select-part_referral, select-table, fixed, hidden, (others can be implemented as httemplate/elements/tr-TYPE.html mason components). Defaults to text.
-
-=item options - For checkbox-multiple and select, a list reference of available option values.
-
-=item option_labels - For checkbox-multiple (and select?), a hash reference of availble option values and labels.
-
-=item value - for checkbox, fixed, hidden (also a default for text, money, more?)
-
-=item table - for select-table
-
-=item name_col - for select-table
-
-=item NOTE: See httemplate/elements/select-table.html for a full list of the optinal options for the select-table type
-
-=back
-
-NOTE: A database connection is B<not> yet available when this subroutine is
-executed.
-
-Example:
-
- sub option_fields {
- (
- 'field' => 'description',
-
- 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
-
- 'third_field' => { 'label' => 'Types',
- 'type' => 'checkbox-multiple',
- 'options' => [ 'h', 's' ],
- 'option_labels' => { 'h' => 'Happy',
- 's' => 'Sad',
- },
- );
- }
-
-=cut
-
-#fallback
-sub option_fields {
- ();
-}
-
-=item condition CUSTOMER_EVENT_OBJECT
-
-Condition classes must define a condition method. This method is evaluated
-to determine if the condition has been met. The object which triggered the
-event (an FS::cust_main, FS::cust_bill or FS::cust_pkg object) is passed as
-the first argument. Additional arguments are list of key-value pairs.
-
-To retreive option values, call the option method on the desired option, i.e.:
-
- my( $self, $cust_object, %opts ) = @_;
- $value_of_field = $self->option('field');
-
-Available additional arguments:
-
- $time = $opt{'time'}; #use this instead of time or $^T
-
- $cust_event = $opt{'cust_event'}; #to retreive the cust_event object being tested
-
-Return a true value if the condition has been met, and a false value if it has
-not.
-
-=item condition_sql EVENTTABLE
-
-Condition classes may optionally define a condition_sql method. This B<class>
-method should return an SQL fragment that tests for this condition. The
-fragment is evaluated and a true value of this expression indicates that the
-condition has been met. The event table (cust_main, cust_bill or cust_pkg) is
-passed as an argument.
-
-This method is used for optimizing event queries. You may want to add indices
-for any columns referenced. It is acceptable to return an SQL fragment which
-partially tests the condition; doing so will still reduce the number of
-records which much be returned and tested with the B<condition> method.
-
-=cut
-
-# fallback.
-sub condition_sql {
- my( $class, $eventtable ) = @_;
- #...
- 'true';
-}
-
-=item disabled
-
-Condition classes may optionally define a disabled method. Returning a true
-value disbles the condition entirely.
-
-=cut
-
-sub disabled {
- 0;
-}
-
-=item implicit_flag
-
-This is used internally by the I<once> and I<balance> conditions. You probably
-do B<not> want to define this method for new custom conditions, unless you're
-sure you want B<every> new action to start with your condition.
-
-Condition classes may define an implicit_flag method that returns true to
-indicate that all new events should start with this condition. (Currently,
-condition classes which do so should be applicable to all kinds of
-I<eventtable>s.) The numeric value of the flag also defines the ordering of
-implicit conditions.
-
-=cut
-
-#fallback
-sub implicit_flag { 0; }
-
-=item remove_warning
-
-Again, used internally by the I<once> and I<balance> conditions; probably not
-a good idea for new custom conditions.
-
-Condition classes may define a remove_warning method containing a string
-warning message to enable a confirmation dialog triggered when the condition
-is removed from an event.
-
-=cut
-
-#fallback
-sub remove_warning { ''; }
-
-=item order_sql
-
-This is used internally by the I<balance_age> and I<cust_bill_age> conditions
-to declare ordering; probably not of general use for new custom conditions.
-
-=item order_sql_weight
-
-In conjunction with order_sql, this defines which order the ordering fragments
-supplied by different B<order_sql> should be used.
-
-=cut
-
-sub order_sql_weight { ''; }
-
-=back
-
-=head1 BASE METHODS
-
-These methods are defined in the base class for use in condition classes.
-
-=over 4
-
-=item cust_main CUST_OBJECT
-
-Return the customer object (see L<FS::cust_main>) associated with the provided
-object (the object itself if it is already a customer object).
-
-=cut
-
-sub cust_main {
- my( $self, $cust_object ) = @_;
-
- $cust_object->isa('FS::cust_main') ? $cust_object : $cust_object->cust_main;
-
-}
-
-=item option_label OPTIONNAME
-
-Returns the label for the specified option name.
-
-=cut
-
-sub option_label {
- my( $self, $optionname ) = @_;
-
- my %option_fields = $self->option_fields;
-
- ref( $option_fields{$optionname} )
- ? $option_fields{$optionname}->{'label'}
- : $option_fields{$optionname}
- or $optionname;
-}
-
-=back
-
-=item condition_sql_option OPTION
-
-This is a class method that returns an SQL fragment for retreiving a condition
-option. It is primarily intended for use in B<condition_sql>.
-
-=cut
-
-sub condition_sql_option {
- my( $class, $option ) = @_;
-
- ( my $condname = $class ) =~ s/^.*:://;
-
- "( SELECT optionvalue FROM part_event_condition_option
- WHERE part_event_condition_option.eventconditionnum =
- cond_$condname.eventconditionnum
- AND part_event_condition_option.optionname = '$option'
- )";
-}
-
-=item condition_sql_option_age_from OPTION FROM_TIMESTAMP
-
-This is a class method that returns an SQL fragment that will retreive a
-condition option, parse it from a frequency (such as "1d", "1w" or "12m"),
-and subtract that interval from the supplied timestamp. It is primarily
-intended for use in B<condition_sql>.
-
-=cut
-
-sub condition_sql_option_age_from {
- my( $class, $option, $from ) = @_;
-
- my $value = $class->condition_sql_option($option);
-
-# my $str2time = str2time_sql;
-
- if ( driver_name =~ /^Pg/i ) {
-
- #can we do better with Pg now that we have $from? yes we can, bob
- "( $from - EXTRACT( EPOCH FROM REPLACE( $value, 'm', 'mon')::interval ) )";
-
- } elsif ( driver_name =~ /^mysql/i ) {
-
- #hmm... is there a way we can save $value? we're just an expression, hmm
- #we might be able to do something like "AS ${option}_value" except we get
- #used in more complicated expressions and we need some sort of unique
- #identifer passed down too... yow
-
- "CASE WHEN $value IS NULL OR $value = ''
- THEN $from
- WHEN $value LIKE '%m'
- THEN UNIX_TIMESTAMP(
- FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'm', '' ) MONTH
- )
- WHEN $value LIKE '%y'
- THEN UNIX_TIMESTAMP(
- FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'y', '' ) YEAR
- )
- WHEN $value LIKE '%w'
- THEN UNIX_TIMESTAMP(
- FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'w', '' ) WEEK
- )
- WHEN $value LIKE '%d'
- THEN UNIX_TIMESTAMP(
- FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'd', '' ) DAY
- )
- WHEN $value LIKE '%h'
- THEN UNIX_TIMESTAMP(
- FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'h', '' ) HOUR
- )
- END
- "
- } else {
-
- die "FATAL: don't know how to subtract frequencies from dates for ".
- driver_name. " databases";
-
- }
-
-}
-
-=item condition_sql_option_age OPTION
-
-This is a class method that returns an SQL fragment for retreiving a condition
-option, and additionaly parsing it from a frequency (such as "1d", "1w" or
-"12m") into an approximate number of seconds.
-
-Note that since months vary in length, the results of this method should B<not>
-be used in computations (use condition_sql_option_age_from for that). They are
-useful for for ordering and comparison to other ages.
-
-This method is primarily intended for use in B<order_sql>.
-
-=cut
-
-sub condition_sql_option_age {
- my( $class, $option ) = @_;
- $class->age2seconds_sql( $class->condition_sql_option($option) );
-}
-
-=item age2seconds_sql
-
-Class method returns an SQL fragment for parsing an arbitrary frequeny (such
-as "1d", "1w", "12m", "2y" or "12h") into an approximate number of seconds.
-
-Approximate meaning: months are considered to be 30 days, years to be
-365.25 days. Otherwise the numbers of seconds returned is exact.
-
-=cut
-
-sub age2seconds_sql {
- my( $class, $value ) = @_;
-
- if ( driver_name =~ /^Pg/i ) {
-
- "EXTRACT( EPOCH FROM REPLACE( $value, 'm', 'mon')::interval )";
-
- } elsif ( driver_name =~ /^mysql/i ) {
-
- #hmm... is there a way we can save $value? we're just an expression, hmm
- #we might be able to do something like "AS ${option}_age" except we get
- #used in more complicated expressions and we need some sort of unique
- #identifer passed down too... yow
- # 2592000 = 30d "1 month"
- # 31557600 = 365.25d "1 year"
-
- "CASE WHEN $value IS NULL OR $value = ''
- THEN 0
- WHEN $value LIKE '%m'
- THEN REPLACE( $value, 'm', '' ) * 2592000
- WHEN $value LIKE '%y'
- THEN REPLACE( $value, 'y', '' ) * 31557600
- WHEN $value LIKE '%w'
- THEN REPLACE( $value, 'w', '' ) * 604800
- WHEN $value LIKE '%d'
- THEN REPLACE( $value, 'd', '' ) * 86400
- WHEN $value LIKE '%h'
- THEN REPLACE( $value, 'h', '' ) * 3600
- END
- "
- } else {
-
- die "FATAL: don't know how to approximate frequencies for ". driver_name.
- " databases";
-
- }
-
-}
-
-=head1 NEW CONDITION CLASSES
-
-A module should be added in FS/FS/part_event/Condition/ which implements the
-methods desribed above in L</METHODS>. An example may be found in the
-eg/part_event-Condition-template.pm file.
-
-=cut
-
-1;
-
-
diff --git a/FS/FS/part_event/Condition/agent.pm b/FS/FS/part_event/Condition/agent.pm
deleted file mode 100644
index da428c1..0000000
--- a/FS/FS/part_event/Condition/agent.pm
+++ /dev/null
@@ -1,37 +0,0 @@
-package FS::part_event::Condition::agent;
-
-use strict;
-
-use base qw( FS::part_event::Condition );
-
-# see the FS::part_event::Condition manpage for full documentation on each
-# of the required and optional methods.
-
-sub description {
- 'Agent';
-}
-
-sub option_fields {
- (
- 'agentnum' => { label=>'Agent', type=>'select-agent', },
- );
-}
-
-sub condition {
- my($self, $object, %opt) = @_;
-
- my $cust_main = $self->cust_main($object);
-
- my $agentnum = $self->option('agentnum');
-
- $cust_main->agentnum == $agentnum;
-
-}
-
-#sub condition_sql {
-# my( $self, $table ) = @_;
-#
-# 'true';
-#}
-
-1;
diff --git a/FS/FS/part_event/Condition/agent_type.pm b/FS/FS/part_event/Condition/agent_type.pm
deleted file mode 100644
index 54c8932..0000000
--- a/FS/FS/part_event/Condition/agent_type.pm
+++ /dev/null
@@ -1,40 +0,0 @@
-package FS::part_event::Condition::agent_type;
-
-use strict;
-
-use base qw( FS::part_event::Condition );
-
-# see the FS::part_event::Condition manpage for full documentation on each
-# of the required and optional methods.
-
-sub description {
- 'Agent Type';
-}
-
-sub option_fields {
- (
- 'typenum' => { label => 'Agent Type',
- type => 'select-agent_type',
- disable_empty => 1,
- },
- );
-}
-
-sub condition {
- my($self, $object, %opt) = @_;
-
- my $cust_main = $self->cust_main($object);
-
- my $typenum = $self->option('typenum');
-
- $cust_main->agent->typenum == $typenum;
-
-}
-
-#sub condition_sql {
-# my( $self, $table ) = @_;
-#
-# 'true';
-#}
-
-1;
diff --git a/FS/FS/part_event/Condition/balance.pm b/FS/FS/part_event/Condition/balance.pm
deleted file mode 100644
index 2639413..0000000
--- a/FS/FS/part_event/Condition/balance.pm
+++ /dev/null
@@ -1,48 +0,0 @@
-package FS::part_event::Condition::balance;
-
-use strict;
-use FS::cust_main;
-
-use base qw( FS::part_event::Condition );
-
-sub description { 'Customer balance'; }
-
-sub implicit_flag { 20; }
-
-sub remove_warning {
- 'Are you sure you want to remove this condition? Doing so will allow this event to run even if the customer has no outstanding balance. Perhaps you want to reset "Balance over" to 0 instead of removing the condition entirely?'; #better error msg?
-}
-
-sub option_fields {
- (
- 'balance' => { 'label' => 'Balance over',
- 'type' => 'money',
- 'value' => '0.00', #default
- },
- );
-}
-
-sub condition {
- my($self, $object) = @_;
-
- my $cust_main = $self->cust_main($object);
-
- my $over = $self->option('balance');
- $over = 0 unless length($over);
-
- $cust_main->balance > $over;
-}
-
-sub condition_sql {
- my( $class, $table ) = @_;
-
- my $over = $class->condition_sql_option('balance');
-
- my $balance_sql = FS::cust_main->balance_sql;
-
- "$balance_sql > $over";
-
-}
-
-1;
-
diff --git a/FS/FS/part_event/Condition/balance_age.pm b/FS/FS/part_event/Condition/balance_age.pm
deleted file mode 100644
index ec3624a..0000000
--- a/FS/FS/part_event/Condition/balance_age.pm
+++ /dev/null
@@ -1,77 +0,0 @@
-package FS::part_event::Condition::balance_age;
-
-require 5.006;
-use strict;
-use Time::Local qw(timelocal_nocheck);
-
-use base qw( FS::part_event::Condition );
-
-sub description { 'Customer balance age'; }
-
-sub option_fields {
- (
- 'balance' => { 'label' => 'Balance over',
- 'type' => 'money',
- 'value' => '0.00', #default
- },
- 'age' => { 'label' => 'Age',
- 'type' => 'freq',
- },
- );
-}
-
-sub condition {
- my($self, $object, %opt) = @_;
-
- my $cust_main = $self->cust_main($object);
-
- my $over = $self->option('balance');
- $over = 0 unless length($over);
-
- #false laziness w/cust_bill_age
- my $time = $opt{'time'};
- my $age = $self->option('age');
- $age = '0m' unless length($age);
-
- my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5];
- if ( $age =~ /^(\d+)m$/i ) {
- $mon -= $1;
- until ( $mon >= 0 ) { $mon += 12; $year--; }
- } elsif ( $age =~ /^(\d+)y$/i ) {
- $year -= $1;
- } elsif ( $age =~ /^(\d+)w$/i ) {
- $mday -= $1 * 7;
- } elsif ( $age =~ /^(\d+)d$/i ) {
- $mday -= $1;
- } elsif ( $age =~ /^(\d+)h$/i ) {
- $hour -= $hour;
- } else {
- die "unparsable age: $age";
- }
- my $age_date = timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year);
-
- $cust_main->balance_date($age_date) > $over;
-}
-
-sub condition_sql {
- my( $class, $table, %opt ) = @_;
-
- my $over = $class->condition_sql_option('balance');
- my $age = $class->condition_sql_option_age_from('age', $opt{'time'});
-
- my $balance_sql = FS::cust_main->balance_date_sql( $age );
-
- "$balance_sql > $over";
-}
-
-sub order_sql {
- shift->condition_sql_option_age('age');
-}
-
-use FS::UID qw( driver_name );
-
-sub order_sql_weight {
- 10;
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/balance_under.pm b/FS/FS/part_event/Condition/balance_under.pm
deleted file mode 100644
index 5e19034..0000000
--- a/FS/FS/part_event/Condition/balance_under.pm
+++ /dev/null
@@ -1,42 +0,0 @@
-package FS::part_event::Condition::balance_under;
-
-use strict;
-use FS::cust_main;
-
-use base qw( FS::part_event::Condition );
-
-sub description { 'Customer balance (under)'; }
-
-sub option_fields {
- (
- 'balance' => { 'label' => 'Balance under (or equal to)',
- 'type' => 'money',
- 'value' => '0.00', #default
- },
- );
-}
-
-sub condition {
- my($self, $object) = @_;
-
- my $cust_main = $self->cust_main($object);
-
- my $under = $self->option('balance');
- $under = 0 unless length($under);
-
- $cust_main->balance <= $under;
-}
-
-sub condition_sql {
- my( $class, $table ) = @_;
-
- my $under = $class->condition_sql_option('balance');
-
- my $balance_sql = FS::cust_main->balance_sql;
-
- "$balance_sql <= $under";
-
-}
-
-1;
-
diff --git a/FS/FS/part_event/Condition/cust_bill_age.pm b/FS/FS/part_event/Condition/cust_bill_age.pm
deleted file mode 100644
index 5c1e468..0000000
--- a/FS/FS/part_event/Condition/cust_bill_age.pm
+++ /dev/null
@@ -1,75 +0,0 @@
-package FS::part_event::Condition::cust_bill_age;
-
-require 5.006;
-use strict;
-use Time::Local qw(timelocal_nocheck);
-
-use base qw( FS::part_event::Condition );
-
-sub description {
- 'Invoice age';
-}
-
-sub eventtable_hashref {
- { 'cust_main' => 0,
- 'cust_bill' => 1,
- 'cust_pkg' => 0,
- };
-}
-
-#something like this
-sub option_fields {
- (
- #'days' => { label=>'Days', size=>3, },
- 'age' => { label=>'Age', type=>'freq', },
- );
-}
-
-sub condition {
- my( $self, $cust_bill, %opt ) = @_;
-
- #false laziness w/balance_age
- my $time = $opt{'time'};
- my $age = $self->option('age');
- $age = '0m' unless length($age);
-
- my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5];
- if ( $age =~ /^(\d+)m$/i ) {
- $mon -= $1;
- until ( $mon >= 0 ) { $mon += 12; $year--; }
- } elsif ( $age =~ /^(\d+)y$/i ) {
- $year -= $1;
- } elsif ( $age =~ /^(\d+)w$/i ) {
- $mday -= $1 * 7;
- } elsif ( $age =~ /^(\d+)d$/i ) {
- $mday -= $1;
- } elsif ( $age =~ /^(\d+)h$/i ) {
- $hour -= $hour;
- } else {
- die "unparsable age: $age";
- }
- my $age_date = timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year);
-
- $cust_bill->_date <= $age_date;
-
-}
-
-# and seconds <= $time - cust_bill._date
-
-sub condition_sql {
- my( $class, $table, %opt ) = @_;
-
- my $age = $class->condition_sql_option_age_from('age', $opt{'time'} );
-
- "cust_bill._date <= $age";
-}
-
-sub order_sql {
- shift->condition_sql_option_age('age');
-}
-
-sub order_sql_weight {
- 0;
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/cust_bill_has_service.pm b/FS/FS/part_event/Condition/cust_bill_has_service.pm
deleted file mode 100644
index be7ea2b..0000000
--- a/FS/FS/part_event/Condition/cust_bill_has_service.pm
+++ /dev/null
@@ -1,54 +0,0 @@
-package FS::part_event::Condition::cust_bill_has_service;
-
-use strict;
-use FS::cust_bill;
-
-use base qw( FS::part_event::Condition );
-
-sub description {
- 'Invoice is billing for a certain service type';
-}
-
-sub eventtable_hashref {
- { 'cust_main' => 0,
- 'cust_bill' => 1,
- 'cust_pkg' => 0,
- };
-}
-
-# could not find component for path '/elements/tr-select-part_svc.html'
-# sub disabled { 1; }
-
-sub option_fields {
- (
- 'has_service' => { 'label' => 'Has service',
- 'type' => 'select-part_svc',
- },
- );
-}
-
-sub condition {
- #my($self, $cust_bill, %opt) = @_;
- my($self, $cust_bill) = @_;
-
- my $servicenum = $self->option('has_service');
- grep { $servicenum == $_->svcnum }
- map { $_->cust_pkg->cust_svc }
- $cust_bill->cust_bill_pkg ;
-}
-
-sub condition_sql {
- my( $class, $table ) = @_;
-
- my $servicenum = $class->condition_sql_option('has_service');
- my $sql = qq| 0 < ( SELECT COUNT(cs.svcpart)
- FROM cust_bill_pkg cbp, cust_svc cs
- WHERE cbp.invnum = cust_bill.invnum
- AND cs.pkgnum = cbp.pkgnum
- AND cs.svcpart = $servicenum
- )
- |;
- return $sql;
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/cust_bill_owed.pm b/FS/FS/part_event/Condition/cust_bill_owed.pm
deleted file mode 100644
index 5e582ef..0000000
--- a/FS/FS/part_event/Condition/cust_bill_owed.pm
+++ /dev/null
@@ -1,54 +0,0 @@
-package FS::part_event::Condition::cust_bill_owed;
-
-use strict;
-use FS::cust_bill;
-
-use base qw( FS::part_event::Condition );
-
-sub description {
- 'Amount owed on specific invoice';
-}
-
-sub eventtable_hashref {
- { 'cust_main' => 0,
- 'cust_bill' => 1,
- 'cust_pkg' => 0,
- };
-}
-
-sub implicit_flag { 30; }
-
-sub remove_warning {
- 'Are you sure you want to remove this condition? Doing so will allow this event to run even for invoices which have no outstanding balance. Perhaps you want to reset "Amount owed over" to 0 instead of removing the condition entirely?'; #better error msg?
-}
-
-sub option_fields {
- (
- 'owed' => { 'label' => 'Amount owed over',
- 'type' => 'money',
- 'value' => '0.00', #default
- },
- );
-}
-
-sub condition {
- #my($self, $cust_bill, %opt) = @_;
- my($self, $cust_bill) = @_;
-
- my $over = $self->option('owed');
- $over = 0 unless length($over);
-
- $cust_bill->owed > $over;
-}
-
-sub condition_sql {
- my( $class, $table ) = @_;
-
- my $over = $class->condition_sql_option('owed');
-
- my $owed_sql = FS::cust_bill->owed_sql;
-
- "$owed_sql > $over";
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/cust_bill_owed_under.pm b/FS/FS/part_event/Condition/cust_bill_owed_under.pm
deleted file mode 100644
index 460e6a4..0000000
--- a/FS/FS/part_event/Condition/cust_bill_owed_under.pm
+++ /dev/null
@@ -1,49 +0,0 @@
-package FS::part_event::Condition::cust_bill_owed_under;
-
-use strict;
-use FS::cust_bill;
-
-use base qw( FS::part_event::Condition );
-
-sub description {
- 'Amount owed on specific invoice (under)';
-}
-
-sub eventtable_hashref {
- { 'cust_main' => 0,
- 'cust_bill' => 1,
- 'cust_pkg' => 0,
- };
-}
-
-sub option_fields {
- (
- 'owed' => { 'label' => 'Amount owed under (or equal to)',
- 'type' => 'money',
- 'value' => '0.00', #default
- },
- );
-}
-
-sub condition {
- #my($self, $cust_bill, %opt) = @_;
- my($self, $cust_bill) = @_;
-
- my $under = $self->option('owed');
- $under = 0 unless length($under);
-
- $cust_bill->owed <= $under;
-
-}
-
-sub condition_sql {
- my( $class, $table ) = @_;
-
- my $under = $class->condition_sql_option('owed');
-
- my $owed_sql = FS::cust_bill->owed_sql;
-
- "$owed_sql <= $under";
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/cust_pay_batch_declined.pm b/FS/FS/part_event/Condition/cust_pay_batch_declined.pm
deleted file mode 100644
index b3a8d70..0000000
--- a/FS/FS/part_event/Condition/cust_pay_batch_declined.pm
+++ /dev/null
@@ -1,51 +0,0 @@
-package FS::part_event::Condition::cust_pay_batch_declined;
-
-use strict;
-
-use base qw( FS::part_event::Condition );
-
-sub description {
- 'Batch payment declined';
-}
-
-sub eventtable_hashref {
- { 'cust_main' => 0,
- 'cust_bill' => 0,
- 'cust_pkg' => 0,
- 'cust_pay_batch' => 1,
- };
-}
-
-#sub option_fields {
-# (
-# 'field' => 'description',
-#
-# 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
-#
-# 'third_field' => { 'label' => 'Types',
-# 'type' => 'checkbox-multiple',
-# 'options' => [ 'h', 's' ],
-# 'option_labels' => { 'h' => 'Happy',
-# 's' => 'Sad',
-# },
-# );
-#}
-
-sub condition {
- my($self, $cust_pay_batch, %opt) = @_;
-
- #my $cust_main = $self->cust_main($object);
- #my $value_of_field = $self->option('field');
- #my $time = $opt{'time'}; #use this instead of time or $^T
-
- $cust_pay_batch->status =~ /Declined/i;
-
-}
-
-#sub condition_sql {
-# my( $class, $table ) = @_;
-# #...
-# 'true';
-#}
-
-1;
diff --git a/FS/FS/part_event/Condition/cust_status.pm b/FS/FS/part_event/Condition/cust_status.pm
deleted file mode 100644
index fbdff25..0000000
--- a/FS/FS/part_event/Condition/cust_status.pm
+++ /dev/null
@@ -1,32 +0,0 @@
-package FS::part_event::Condition::cust_status;
-
-use strict;
-
-use base qw( FS::part_event::Condition );
-use FS::Record qw( qsearch );
-
-sub description {
- 'Customer Status';
-}
-
-#something like this
-sub option_fields {
- (
- 'status' => { 'label' => 'Customer Status',
- 'type' => 'select-cust_main-status',
- 'multiple' => 1,
- },
- );
-}
-
-sub condition {
- my( $self, $object) = @_;
-
- my $cust_main = $self->cust_main($object);
-
- #XXX test
- my $hashref = $self->option('status') || {};
- $hashref->{ $cust_main->status };
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/every.pm b/FS/FS/part_event/Condition/every.pm
deleted file mode 100644
index 3408b0a..0000000
--- a/FS/FS/part_event/Condition/every.pm
+++ /dev/null
@@ -1,67 +0,0 @@
-package FS::part_event::Condition::every;
-
-use strict;
-use FS::UID qw( dbh );
-use FS::Record qw( qsearch );
-use FS::cust_event;
-
-use base qw( FS::part_event::Condition );
-
-sub description { "Don't retry failures more often than specified interval"; }
-
-sub option_fields {
- (
- 'retry_delay' => { label=>'Retry after', type=>'freq', value=>'1d', },
- 'max_tries' => { label=>'Maximum # of attempts', type=>'text', size=>3, },
- );
-}
-
-my %after = (
- 'h' => 3600,
- 'd' => 86400,
- 'w' => 604800,
- 'm' => 2592000, #well, 30 days... presumably people would mostly use d or w
- '' => 2592000,
- 'y' => 31536000, #well, 365 days...
-);
-
-my $sql =
- "SELECT COUNT(*) FROM cust_event WHERE eventpart = ? AND tablenum = ?";
-
-sub condition {
- my($self, $object, %opt) = @_;
-
- my $obj_pkey = $object->primary_key;
- my $tablenum = $object->$obj_pkey();
-
- if ( $self->option('max_tries') =~ /^\s*(\d+)\s*$/ ) {
- my $max_tries = $1;
- my $sth = dbh->prepare($sql)
- or die dbh->errstr. " preparing: $sql";
- $sth->execute($self->eventpart, $tablenum)
- or die $sth->errstr. " executing: $sql";
- my $tries = $sth->fetchrow_arrayref->[0];
- return 0 if $tries >= $max_tries;
- }
-
- my $time = $opt{'time'};
- my $retry_delay = $self->option('retry_delay');
- $retry_delay =~ /^(\d+)([hdwmy]?)$/
- or die "unparsable retry_delay: $retry_delay";
- my $date_after = $time - $1 * $after{$2};
-
- my $sth = dbh->prepare("$sql AND date > ?") # AND status = 'failed' "
- or die dbh->errstr. " preparing: $sql";
- $sth->execute($self->eventpart, $tablenum, $date_after)
- or die $sth->errstr. " executing: $sql";
- ! $sth->fetchrow_arrayref->[0];
-
-}
-
-#sub condition_sql {
-# my( $self, $table ) = @_;
-#
-# 'true';
-#}
-
-1;
diff --git a/FS/FS/part_event/Condition/once.pm b/FS/FS/part_event/Condition/once.pm
deleted file mode 100644
index 5a9161f..0000000
--- a/FS/FS/part_event/Condition/once.pm
+++ /dev/null
@@ -1,55 +0,0 @@
-package FS::part_event::Condition::once;
-
-use strict;
-use FS::Record qw( qsearch );
-use FS::part_event;
-use FS::cust_event;
-
-use base qw( FS::part_event::Condition );
-
-sub description { "Don't run this event again after it has completed sucessfully"; }
-
-sub implicit_flag { 10; }
-
-sub remove_warning {
- 'Are you sure you want to remove this condition? Doing so will allow this event to run every time the other conditions are satisfied, even if it has already run sucessfully.'; #better error msg?
-}
-
-sub condition {
- my($self, $object, %opt) = @_;
-
- my $obj_pkey = $object->primary_key;
- my $tablenum = $object->$obj_pkey();
-
- my @existing = qsearch( {
- 'table' => 'cust_event',
- 'hashref' => {
- 'eventpart' => $self->eventpart,
- 'tablenum' => $tablenum,
- 'status' => { op=>'!=', value=>'failed' },
- },
- 'extra_sql' => ( $opt{'cust_event'}->eventnum =~ /^(\d+)$/
- ? " AND eventnum != $1 "
- : ''
- ),
- } );
-
- ! scalar(@existing);
-
-}
-
-sub condition_sql {
- my( $self, $table ) = @_;
-
- my %tablenum = %{ FS::part_event->eventtable_pkey_sql };
-
- "0 = ( SELECT COUNT(*) FROM cust_event
- WHERE cust_event.eventpart = part_event.eventpart
- AND cust_event.tablenum = $tablenum{$table}
- AND status != 'failed'
- )
- ";
-
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/payby.pm b/FS/FS/part_event/Condition/payby.pm
deleted file mode 100644
index d931568..0000000
--- a/FS/FS/part_event/Condition/payby.pm
+++ /dev/null
@@ -1,50 +0,0 @@
-package FS::part_event::Condition::payby;
-
-use strict;
-use Tie::IxHash;
-use FS::payby;
-
-use base qw( FS::part_event::Condition );
-
-sub description {
- #'customer payment types: ';
- 'Customer payment type';
-}
-
-#something like this
-tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname;
-sub option_fields {
- (
- 'payby' => {
- label => 'Customer payment type',
- #type => 'select-multiple',
- type => 'checkbox-multiple',
- options => [ keys %payby ],
- option_labels => \%payby,
- },
- );
-}
-
-sub condition {
- my( $self, $object ) = @_;
-
- my $cust_main = $self->cust_main($object);
-
- #uuh.. all right? test this.
- my $hashref = $self->option('payby') || {};
- $hashref->{ $cust_main->payby };
-
-}
-
-#sub condition_sql {
-# my( $self, $table ) = @_;
-#
-# #uuh... yeah... something like this. test it for sure.
-#
-# my @payby = keys %{ $self->option('payby') };
-#
-# ' ( '. join(' OR ', map { "cust_main.payby = '$_'" } @payby ). ' ) ';
-#
-#}
-
-1;
diff --git a/FS/FS/part_event/Condition/pkg_class.pm b/FS/FS/part_event/Condition/pkg_class.pm
deleted file mode 100644
index 8c9031c..0000000
--- a/FS/FS/part_event/Condition/pkg_class.pm
+++ /dev/null
@@ -1,38 +0,0 @@
-package FS::part_event::Condition::pkg_class;
-
-use strict;
-
-use base qw( FS::part_event::Condition );
-use FS::Record qw( qsearch );
-use FS::pkg_class;
-
-sub description {
- 'Package Class';
-}
-
-sub eventtable_hashref {
- { 'cust_main' => 0,
- 'cust_bill' => 0,
- 'cust_pkg' => 1,
- };
-}
-
-#something like this
-sub option_fields {
- (
- 'pkgclass' => { 'label' => 'Package Class',
- 'type' => 'select-pkg_class',
- 'multiple' => 1,
- },
- );
-}
-
-sub condition {
- my( $self, $cust_pkg ) = @_;
-
- #XXX test
- my $hashref = $self->option('pkgclass') || {};
- $hashref->{ $cust_pkg->part_pkg->classnum };
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/pkg_status.pm b/FS/FS/part_event/Condition/pkg_status.pm
deleted file mode 100644
index 6c1c9cc..0000000
--- a/FS/FS/part_event/Condition/pkg_status.pm
+++ /dev/null
@@ -1,37 +0,0 @@
-package FS::part_event::Condition::pkg_status;
-
-use strict;
-
-use base qw( FS::part_event::Condition );
-use FS::Record qw( qsearch );
-
-sub description {
- 'Package Status';
-}
-
-sub eventtable_hashref {
- { 'cust_main' => 0,
- 'cust_bill' => 0,
- 'cust_pkg' => 1,
- };
-}
-
-#something like this
-sub option_fields {
- (
- 'status' => { 'label' => 'Package Status',
- 'type' => 'select-cust_pkg-status',
- 'multiple' => 1,
- },
- );
-}
-
-sub condition {
- my( $self, $cust_pkg ) = @_;
-
- #XXX test
- my $hashref = $self->option('status') || {};
- $hashref->{ $cust_pkg->status };
-}
-
-1;
diff --git a/FS/FS/part_event_condition.pm b/FS/FS/part_event_condition.pm
deleted file mode 100644
index d13e849..0000000
--- a/FS/FS/part_event_condition.pm
+++ /dev/null
@@ -1,352 +0,0 @@
-package FS::part_event_condition;
-
-use strict;
-use vars qw( @ISA $DEBUG @SKIP_CONDITION_SQL );
-use FS::UID qw(dbh);
-use FS::Record qw( qsearch qsearchs );
-use FS::option_Common;
-use FS::part_event; #for order_conditions_sql...
-
-@ISA = qw( FS::option_Common ); # FS::Record );
-$DEBUG = 0;
-
-@SKIP_CONDITION_SQL = ();
-
-=head1 NAME
-
-FS::part_event_condition - Object methods for part_event_condition records
-
-=head1 SYNOPSIS
-
- use FS::part_event_condition;
-
- $record = new FS::part_event_condition \%hash;
- $record = new FS::part_event_condition { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_event_condition object represents an event condition.
-FS::part_event_condition inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item eventconditionnum - primary key
-
-=item eventpart - Event definition (see L<FS::part_event>)
-
-=item conditionname - Condition name - defines which FS::part_event::Condition::I<conditionname> evaluates this condition
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new event. To add the example to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_event_condition'; }
-
-=item insert [ HASHREF | OPTION => VALUE ... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-If a list or hash reference of options is supplied, part_event_condition_option
-records are created (see L<FS::part_event_condition_option>).
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD [ HASHREF | OPTION => VALUE ... ]
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-If a list or hash reference of options is supplied, part_event_condition_option
-records are created or modified (see L<FS::part_event_condition_option>).
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid example. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('eventconditionnum')
- || $self->ut_foreign_key('eventpart', 'part_event', 'eventpart')
- || $self->ut_alpha('conditionname')
- ;
- return $error if $error;
-
- #XXX check conditionname to make sure a module exists?
- # well it'll die in _rebless...
-
- $self->SUPER::check;
-}
-
-
-=item _rebless
-
-Reblesses the object into the FS::part_event::Condition::CONDITIONNAME class,
-where CONDITIONNAME is the object's I<conditionname> field.
-
-=cut
-
-sub _rebless {
- my $self = shift;
- my $conditionname = $self->conditionname;
- #my $class = ref($self). "::$conditionname";
- my $class = "FS::part_event::Condition::$conditionname";
- eval "use $class";
- die $@ if $@;
- bless($self, $class); #unless $@;
- $self;
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item conditions [ EVENTTABLE ]
-
-Return information about the available conditions. If an eventtable is
-specified, only return information about conditions available for that
-eventtable.
-
-Information is returned as key-value pairs. Keys are condition names. Values
-are hashrefs with the following keys:
-
-=over 4
-
-=item description
-
-=item option_fields
-
-# =item default_weight
-
-# =item deprecated
-
-=back
-
-See L<FS::part_event::Condition> for more information.
-
-=cut
-
-#false laziness w/part_event.pm
-#some false laziness w/part_export & part_pkg
-my %conditions;
-foreach my $INC ( @INC ) {
- foreach my $file ( glob("$INC/FS/part_event/Condition/*.pm") ) {
- warn "attempting to load Condition from $file\n" if $DEBUG;
- $file =~ /\/(\w+)\.pm$/ or do {
- warn "unrecognized file in $INC/FS/part_event/Condition/: $file\n";
- next;
- };
- my $mod = $1;
- my $fullmod = "FS::part_event::Condition::$mod";
- eval "use $fullmod;";
- if ( $@ ) {
- die "error using $fullmod (skipping): $@\n" if $@;
- #warn "error using $fullmod (skipping): $@\n" if $@;
- #next;
- }
- if ( $fullmod->disabled ) {
- warn "$fullmod is disabled; skipping\n";
- next;
- }
- #my $full_condition_sql = $fullmod. '::condition_sql';
- my $condition_sql_coderef = sub { $fullmod->condition_sql(@_) };
- my $order_sql_coderef = $fullmod->can('order_sql')
- ? sub { $fullmod->order_sql(@_) }
- : '';
- $conditions{$mod} = {
- ( map { $_ => $fullmod->$_() }
- qw( description eventtable_hashref
- implicit_flag remove_warning
- order_sql_weight
- )
- # deprecated
- #option_fields_hashref
- ),
- 'option_fields' => [ $fullmod->option_fields() ],
- 'condition_sql' => $condition_sql_coderef,
- 'order_sql' => $order_sql_coderef,
- };
- }
-}
-
-sub conditions {
- my( $class, $eventtable ) = @_;
- (
- map { $_ => $conditions{$_} }
-# sort { $conditions{$a}->{'default_weight'}<=>$conditions{$b}->{'default_weight'} }
-# sort by ?
- $class->all_conditionnames( $eventtable )
- );
-
-}
-
-=item all_conditionnames [ EVENTTABLE ]
-
-Returns a list of just the condition names
-
-=cut
-
-sub all_conditionnames {
- my ( $class, $eventtable ) = @_;
-
- grep { !$eventtable || $conditions{$_}->{'eventtable_hashref'}{$eventtable} }
- keys %conditions
-}
-
-=item join_conditions_sql [ EVENTTABLE ]
-
-Returns an SQL fragment selecting joining all condition options for an event as
-tables titled "cond_I<conditionname>". Typically used in conjunction with
-B<where_conditions_sql>.
-
-=cut
-
-sub join_conditions_sql {
- my ( $class, $eventtable ) = @_;
- my %conditions = $class->conditions( $eventtable );
-
- join(' ',
- map {
- "LEFT JOIN part_event_condition AS cond_$_".
- " ON ( part_event.eventpart = cond_$_.eventpart".
- " AND cond_$_.conditionname = ". dbh->quote($_).
- " )";
- }
- keys %conditions
- );
-
-}
-
-=item where_conditions_sql [ EVENTTABLE [ , OPTION => VALUE, ... ] ]
-
-Returns an SQL fragment to select events which have unsatisfied conditions.
-Must be used in conjunction with B<join_conditions_sql>.
-
-The only current option is "time", the current time (or "pretend" current time
-as passed to freeside-daily), as a UNIX timestamp.
-
-=cut
-
-sub where_conditions_sql {
- my ( $class, $eventtable, %options ) = @_;
-
- my $time = $options{'time'};
-
- my %conditions = $class->conditions( $eventtable );
-
- my $where = join(' AND ',
- map {
- my $conditionname = $_;
- my $coderef = $conditions{$conditionname}->{condition_sql};
- my $sql = &$coderef( $eventtable, 'time'=>$time );
- die "$coderef is not a CODEREF" unless ref($coderef) eq 'CODE';
- "( cond_$conditionname.conditionname IS NULL OR $sql )";
- }
- grep { my $cond = $_;
- ! grep { $_ eq $cond } @SKIP_CONDITION_SQL
- }
- keys %conditions
- );
-
- $where;
-}
-
-=item order_conditions_sql [ EVENTTABLE ]
-
-Returns an SQL fragment to order selected events. Must be used in conjunction
-with B<join_conditions_sql>.
-
-=cut
-
-sub order_conditions_sql {
- my( $class, $eventtable ) = @_;
-
- my %conditions = $class->conditions( $eventtable );
-
- my $eventtables = join(' ', FS::part_event->eventtables_runorder);
-
- my $order_by = join(', ',
- "position( part_event.eventtable in ' $eventtables ')",
- ( map {
- my $conditionname = $_;
- my $coderef = $conditions{$conditionname}->{order_sql};
- my $sql = &$coderef( $eventtable );
- "CASE WHEN cond_$conditionname.conditionname IS NULL
- THEN -1
- ELSE $sql
- END
- ";
- }
- sort { $conditions{$a}->{order_sql_weight}
- <=> $conditions{$b}->{order_sql_weight}
- }
- grep { $conditions{$_}->{order_sql} }
- keys %conditions
- ),
- 'part_event.weight'
- );
-
- "ORDER BY $order_by";
-
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::part_event::Condition>, L<FS::part_event>, L<FS::Record>, schema.html from
-the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_event_condition_option.pm b/FS/FS/part_event_condition_option.pm
deleted file mode 100644
index 3256dc0..0000000
--- a/FS/FS/part_event_condition_option.pm
+++ /dev/null
@@ -1,151 +0,0 @@
-package FS::part_event_condition_option;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::option_Common;
-use FS::part_event_condition;
-
-@ISA = qw( FS::option_Common ); # FS::Record);
-
-=head1 NAME
-
-FS::part_event_condition_option - Object methods for part_event_condition_option records
-
-=head1 SYNOPSIS
-
- use FS::part_event_condition_option;
-
- $record = new FS::part_event_condition_option \%hash;
- $record = new FS::part_event_condition_option { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_event_condition_option object represents an event condition option.
-FS::part_event_condition_option inherits from FS::Record. The following fields
-are currently supported:
-
-=over 4
-
-=item optionnum - primary key
-
-=item eventconditionnum - Event condition (see L<FS::part_event_condition>)
-
-=item optionname - Option name
-
-=item optionvalue - Option value
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_event_condition_option'; }
-
-=item insert [ HASHREF | OPTION => VALUE ... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-If a list or hash reference of options is supplied,
-part_event_condition_option_option records are created (see
-L<FS::part_event_condition_option_option>).
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD [ HASHREF | OPTION => VALUE ... ]
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-If a list or hash reference of options is supplied,
-part_event_condition_option_option records are created or modified (see
-L<FS::part_event_condition_option_option>).
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('optionnum')
- || $self->ut_foreign_key('eventconditionnum',
- 'part_event_condition', 'eventconditionnum')
- || $self->ut_text('optionname')
- || $self->ut_textn('optionvalue')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-#this makes the nested options magically show up as perl refs
-#move it to a mixin class if we need nested options again
-sub optionvalue {
- my $self = shift;
- if ( scalar(@_) ) { #setting, no magic (here, insert takes care of it)
- $self->set('optionvalue', @_);
- } else { #getting, magic
- my $optionvalue = $self->get('optionvalue');
- if ( $optionvalue eq 'HASH' ) {
- return { $self->options };
- } else {
- $optionvalue;
- }
- }
-}
-
-=back
-
-=head1 SEE ALSO
-
-L<FS::part_event_condition>, L<FS::part_event_condition_option_option>,
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_event_condition_option_option.pm b/FS/FS/part_event_condition_option_option.pm
deleted file mode 100644
index 7396c22..0000000
--- a/FS/FS/part_event_condition_option_option.pm
+++ /dev/null
@@ -1,129 +0,0 @@
-package FS::part_event_condition_option_option;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::part_event_condition_option;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::part_event_condition_option_option - Object methods for part_event_condition_option_option records
-
-=head1 SYNOPSIS
-
- use FS::part_event_condition_option_option;
-
- $record = new FS::part_event_condition_option_option \%hash;
- $record = new FS::part_event_condition_option_option { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_event_condition_option_option object represents a nested event
-condition option. FS::part_event_condition_option_option inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item optionoptionnum - primary key
-
-=item optionnum - Parent option (see L<FS::part_event_option>)
-
-=item optionname - Option name
-
-=item optionvalue - Option value
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_event_condition_option_option'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('optionoptionnum')
- || $self->ut_foreign_key('optionnum',
- 'part_event_condition_option', 'optionnum' )
- || $self->ut_text('optionname')
- || $self->ut_textn('optionvalue')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::part_event_condition_option>, L<FS::Record>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_event_option.pm b/FS/FS/part_event_option.pm
deleted file mode 100644
index 43e1da9..0000000
--- a/FS/FS/part_event_option.pm
+++ /dev/null
@@ -1,213 +0,0 @@
-package FS::part_event_option;
-
-use strict;
-use vars qw( @ISA );
-use FS::UID qw( dbh );
-use FS::Record qw( qsearch qsearchs );
-use FS::part_event;
-use FS::reason;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::part_event_option - Object methods for part_event_option records
-
-=head1 SYNOPSIS
-
- use FS::part_event_option;
-
- $record = new FS::part_event_option \%hash;
- $record = new FS::part_event_option { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_event_option object represents an event definition option (action
-option). FS::part_event_option inherits from FS::Record. The following fields
-are currently supported:
-
-=over 4
-
-=item optionnum - primary key
-
-=item eventpart - Event definition (see L<FS::part_event>)
-
-=item optionname - Option name
-
-=item optionvalue - Option value
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_event_option'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub insert {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ( $self->optionname eq 'reasonnum' && $self->optionvalue eq 'HASH' ) {
-
- my $error = $self->insert_reason(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace [ OLD_RECORD ]
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
- ? shift
- : $self->replace_old;
-
- if ( $self->optionname eq 'reasonnum' ) {
- warn "reasonnum: ". $self->optionvalue;
- }
- if ( $self->optionname eq 'reasonnum' && $self->optionvalue eq 'HASH' ) {
-
- my $error = $self->insert_reason(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- my $error = $self->SUPER::replace($old);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('optionnum')
- || $self->ut_foreign_key('eventpart', 'part_event', 'eventpart' )
- || $self->ut_text('optionname')
- || $self->ut_textn('optionvalue')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-sub insert_reason {
- my( $self, $reason ) = @_;
-
- my $reason_obj = new FS::reason({
- 'reason_type' => $reason->{'typenum'},
- 'reason' => $reason->{'reason'},
- });
-
- $reason_obj->insert or $self->optionvalue( $reason_obj->reasonnum ) and '';
-
-}
-
-=back
-
-=head1 SEE ALSO
-
-L<FS::part_event>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm
deleted file mode 100644
index 983e0b0..0000000
--- a/FS/FS/part_export.pm
+++ /dev/null
@@ -1,469 +0,0 @@
-package FS::part_export;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK $DEBUG %exports );
-use Exporter;
-use Tie::IxHash;
-use FS::Record qw( qsearch qsearchs dbh );
-use FS::option_Common;
-use FS::part_svc;
-use FS::part_export_option;
-use FS::export_svc;
-
-#for export modules, though they should probably just use it themselves
-use FS::queue;
-
-@ISA = qw( FS::option_Common );
-@EXPORT_OK = qw(export_info);
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::part_export - Object methods for part_export records
-
-=head1 SYNOPSIS
-
- use FS::part_export;
-
- $record = new FS::part_export \%hash;
- $record = new FS::part_export { 'column' => 'value' };
-
- #($new_record, $options) = $template_recored->clone( $svcpart );
-
- $error = $record->insert( { 'option' => 'value' } );
- $error = $record->insert( \%options );
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_export object represents an export of Freeside data to an external
-provisioning system. FS::part_export inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item exportnum - primary key
-
-=item machine - Machine name
-
-=item exporttype - Export type
-
-=item nodomain - blank or "Y" : usernames are exported to this service with no domain
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new export. To add the export to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_export'; }
-
-=cut
-
-#=item clone SVCPART
-#
-#An alternate constructor. Creates a new export by duplicating an existing
-#export. The given svcpart is assigned to the new export.
-#
-#Returns a list consisting of the new export object and a hashref of options.
-#
-#=cut
-#
-#sub clone {
-# my $self = shift;
-# my $class = ref($self);
-# my %hash = $self->hash;
-# $hash{'exportnum'} = '';
-# $hash{'svcpart'} = shift;
-# ( $class->new( \%hash ),
-# { map { $_->optionname => $_->optionvalue }
-# qsearch('part_export_option', { 'exportnum' => $self->exportnum } )
-# }
-# );
-#}
-
-=item insert HASHREF
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-If a hash reference of options is supplied, part_export_option records are
-created (see L<FS::part_export_option>).
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-#foreign keys would make this much less tedious... grr dumb mysql
-sub delete {
- my $self = shift;
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- foreach my $export_svc ( $self->export_svc ) {
- my $error = $export_svc->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid export. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
- my $error =
- $self->ut_numbern('exportnum')
- || $self->ut_domain('machine')
- || $self->ut_alpha('exporttype')
- ;
- return $error if $error;
-
- $self->nodomain =~ /^(Y?)$/ or return "Illegal nodomain: ". $self->nodomain;
- $self->nodomain($1);
-
- $self->deprecated(1); #BLAH
-
- #check exporttype?
-
- $self->SUPER::check;
-}
-
-#=item part_svc
-#
-#Returns the service definition (see L<FS::part_svc>) for this export.
-#
-#=cut
-#
-#sub part_svc {
-# my $self = shift;
-# qsearchs('part_svc', { svcpart => $self->svcpart } );
-#}
-
-sub part_svc {
- use Carp;
- croak "FS::part_export::part_svc deprecated";
- #confess "FS::part_export::part_svc deprecated";
-}
-
-=item svc_x
-
-Returns a list of associated FS::svc_* records.
-
-=cut
-
-sub svc_x {
- my $self = shift;
- map { $_->svc_x } $self->cust_svc;
-}
-
-=item cust_svc
-
-Returns a list of associated FS::cust_svc records.
-
-=cut
-
-sub cust_svc {
- my $self = shift;
- map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
- grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
- $self->export_svc;
-}
-
-=item export_svc
-
-Returns a list of associated FS::export_svc records.
-
-=cut
-
-sub export_svc {
- my $self = shift;
- qsearch('export_svc', { 'exportnum' => $self->exportnum } );
-}
-
-=item part_export_option
-
-Returns all options as FS::part_export_option objects (see
-L<FS::part_export_option>).
-
-=cut
-
-sub part_export_option {
- my $self = shift;
- $self->option_objects;
-}
-
-=item options
-
-Returns a list of option names and values suitable for assigning to a hash.
-
-=item option OPTIONNAME
-
-Returns the option value for the given name, or the empty string.
-
-=item _rebless
-
-Reblesses the object into the FS::part_export::EXPORTTYPE class, where
-EXPORTTYPE is the object's I<exporttype> field. There should be better docs
-on how to create new exports, but until then, see L</NEW EXPORT CLASSES>.
-
-=cut
-
-sub _rebless {
- my $self = shift;
- my $exporttype = $self->exporttype;
- my $class = ref($self). "::$exporttype";
- eval "use $class;";
- #die $@ if $@;
- bless($self, $class) unless $@;
- $self;
-}
-
-#these should probably all go away, just let the subclasses define em
-
-=item export_insert SVC_OBJECT
-
-=cut
-
-sub export_insert {
- my $self = shift;
- #$self->rebless;
- $self->_export_insert(@_);
-}
-
-#sub AUTOLOAD {
-# my $self = shift;
-# $self->rebless;
-# my $method = $AUTOLOAD;
-# #$method =~ s/::(\w+)$/::_$1/; #infinite loop prevention
-# $method =~ s/::(\w+)$/_$1/; #infinite loop prevention
-# $self->$method(@_);
-#}
-
-=item export_replace NEW OLD
-
-=cut
-
-sub export_replace {
- my $self = shift;
- #$self->rebless;
- $self->_export_replace(@_);
-}
-
-=item export_delete
-
-=cut
-
-sub export_delete {
- my $self = shift;
- #$self->rebless;
- $self->_export_delete(@_);
-}
-
-=item export_suspend
-
-=cut
-
-sub export_suspend {
- my $self = shift;
- #$self->rebless;
- $self->_export_suspend(@_);
-}
-
-=item export_unsuspend
-
-=cut
-
-sub export_unsuspend {
- my $self = shift;
- #$self->rebless;
- $self->_export_unsuspend(@_);
-}
-
-#fallbacks providing useful error messages intead of infinite loops
-sub _export_insert {
- my $self = shift;
- return "_export_insert: unknown export type ". $self->exporttype;
-}
-
-sub _export_replace {
- my $self = shift;
- return "_export_replace: unknown export type ". $self->exporttype;
-}
-
-sub _export_delete {
- my $self = shift;
- return "_export_delete: unknown export type ". $self->exporttype;
-}
-
-#call svcdb-specific fallbacks
-
-sub _export_suspend {
- my $self = shift;
- #warn "warning: _export_suspened unimplemented for". ref($self);
- my $svc_x = shift;
- my $new = $svc_x->clone_suspended;
- $self->_export_replace( $new, $svc_x );
-}
-
-sub _export_unsuspend {
- my $self = shift;
- #warn "warning: _export_unsuspend unimplemented for ". ref($self);
- my $svc_x = shift;
- my $old = $svc_x->clone_kludge_unsuspend;
- $self->_export_replace( $svc_x, $old );
-}
-
-=item export_links SVC_OBJECT ARRAYREF
-
-Adds a list of web elements to ARRAYREF specific to this export and SVC_OBJECT.
-The elements are displayed in the UI to lead the the operator to external
-configuration, monitoring, and similar tools.
-
-=cut
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item export_info [ SVCDB ]
-
-Returns a hash reference of the exports for the given I<svcdb>, or if no
-I<svcdb> is specified, for all exports. The keys of the hash are
-I<exporttype>s and the values are again hash references containing information
-on the export:
-
- 'desc' => 'Description',
- 'options' => {
- 'option' => { label=>'Option Label' },
- 'option2' => { label=>'Another label' },
- },
- 'nodomain' => 'Y', #or ''
- 'notes' => 'Additional notes',
-
-=cut
-
-sub export_info {
- #warn $_[0];
- return $exports{$_[0]} || {} if @_;
- #{ map { %{$exports{$_}} } keys %exports };
- my $r = { map { %{$exports{$_}} } keys %exports };
-}
-
-#=item exporttype2svcdb EXPORTTYPE
-#
-#Returns the applicable I<svcdb> for an I<exporttype>.
-#
-#=cut
-#
-#sub exporttype2svcdb {
-# my $exporttype = $_[0];
-# foreach my $svcdb ( keys %exports ) {
-# return $svcdb if grep { $exporttype eq $_ } keys %{$exports{$svcdb}};
-# }
-# '';
-#}
-
-foreach my $INC ( @INC ) {
- foreach my $file ( glob("$INC/FS/part_export/*.pm") ) {
- warn "attempting to load export info from $file\n" if $DEBUG;
- $file =~ /\/(\w+)\.pm$/ or do {
- warn "unrecognized file in $INC/FS/part_export/: $file\n";
- next;
- };
- my $mod = $1;
- my $info = eval "use FS::part_export::$mod; ".
- "\\%FS::part_export::$mod\::info;";
- if ( $@ ) {
- die "error using FS::part_export::$mod (skipping): $@\n" if $@;
- next;
- }
- unless ( keys %$info ) {
- warn "no %info hash found in FS::part_export::$mod, skipping\n"
- unless $mod =~ /^(passwdfile|null)$/; #hack but what the heck
- next;
- }
- warn "got export info from FS::part_export::$mod: $info\n" if $DEBUG;
- no strict 'refs';
- foreach my $svc (
- ref($info->{'svc'}) ? @{$info->{'svc'}} : $info->{'svc'}
- ) {
- unless ( $svc ) {
- warn "blank svc for FS::part_export::$mod (skipping)\n";
- next;
- }
- $exports{$svc}->{$mod} = $info;
- }
- }
-}
-
-=back
-
-=head1 NEW EXPORT CLASSES
-
-A module should be added in FS/FS/part_export/ (an example may be found in
-eg/export_template.pm)
-
-=head1 BUGS
-
-Hmm... cust_export class (not necessarily a database table...) ... ?
-
-deprecated column...
-
-=head1 SEE ALSO
-
-L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_acct>,
-L<FS::svc_domain>,
-L<FS::svc_forward>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_export/acct_plesk.pm b/FS/FS/part_export/acct_plesk.pm
deleted file mode 100644
index 1be820a..0000000
--- a/FS/FS/part_export/acct_plesk.pm
+++ /dev/null
@@ -1,121 +0,0 @@
-package FS::part_export::acct_plesk;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'URL' => { label=>'URL' },
- 'login' => { label=>'Login' },
- 'password' => { label=>'Password' },
- 'debug' => { label=>'Enable debugging',
- type=>'checkbox' },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to Plesk managed mail service',
- 'options'=> \%options,
- 'notes' => <<'END'
-Real-time export to
-<a href="http://www.swsoft.com/">Plesk</a> managed server.
-Requires installation of
-<a href="http://search.cpan.org/dist/Net-Plesk">Net::Plesk</a>
-from CPAN.
-END
-);
-
-sub rebless { shift; }
-
-# experiment: want the status of these right away (don't want account to
-# create or whatever and then get error in the queue from dup username or
-# something), so no queueing
-
-sub _export_insert {
- my( $self, $svc_acct ) = (shift, shift);
-
- $self->_plesk_command( 'mail_add',
- $svc_acct->domain,
- $svc_acct->username,
- $svc_acct->_password,
- ) ||
- $self->_export_unsuspend($svc_acct);
-}
-
-sub _plesk_command {
- my( $self, $method, $domain, @args ) = @_;
-
- eval "use Net::Plesk;";
- return $@ if $@;
-
- local($Net::Plesk::DEBUG) = 1
- if $self->option('debug');
-
- my $plesk = new Net::Plesk (
- 'POST' => $self->option('URL'),
- ':HTTP_AUTH_LOGIN' => $self->option('login'),
- ':HTTP_AUTH_PASSWD' => $self->option('password'),
- );
-
- my $dresponse = $plesk->domain_get( $domain );
- return $dresponse->errortext unless $dresponse->is_success;
- my $domainID = $dresponse->id;
-
- my $response = $plesk->$method($dresponse->id, @args);
- return $response->errortext unless $response->is_success;
- '';
-
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
-
- return "can't change domain with Plesk"
- if $old->domain ne $new->domain;
- return "can't change username with Plesk"
- if $old->username ne $new->username;
- return '' unless $old->_password ne $new->_password;
-
- $self->_plesk_command( 'mail_set',
- $new->domain,
- $new->username,
- $new->_password,
- $old->cust_svc->cust_pkg->susp ? 0 : 1,
- );
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
-
- $self->_plesk_command( 'mail_remove',
- $svc_acct->domain,
- $svc_acct->username,
- );
-}
-
-sub _export_suspend {
- my( $self, $svc_acct ) = (shift, shift);
-
- $self->_plesk_command( 'mail_set',
- $svc_acct->domain,
- $svc_acct->username,
- $svc_acct->_password,
- 0,
- );
-}
-
-sub _export_unsuspend {
- my( $self, $svc_acct ) = (shift, shift);
-
- $self->_plesk_command( 'mail_set',
- $svc_acct->domain,
- $svc_acct->username,
- $svc_acct->_password,
- 1,
- );
-}
-
-1;
-
diff --git a/FS/FS/part_export/acct_sql.pm b/FS/FS/part_export/acct_sql.pm
deleted file mode 100644
index 9f1ae7b..0000000
--- a/FS/FS/part_export/acct_sql.pm
+++ /dev/null
@@ -1,310 +0,0 @@
-package FS::part_export::acct_sql;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-#use Digest::MD5 qw(md5_hex);
-use FS::Record; #qw(qsearchs);
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'datasrc' => { label => 'DBI data source' },
- 'username' => { label => 'Database username' },
- 'password' => { label => 'Database password' },
- 'table' => { label => 'Database table' },
- 'schema' => { label =>
- 'Database schema mapping to Freeside methods.',
- type => 'textarea',
- },
- 'static' => { label =>
- 'Database schema mapping to static values.',
- type => 'textarea',
- },
- 'primary_key' => { label => 'Database primary key' },
- 'crypt' => { label => 'Password encryption',
- type=>'select', options=>[qw(crypt md5)],
- default=>'crypt',
- },
-;
-
-tie my %vpopmail_map, 'Tie::IxHash',
- 'pw_name' => 'username',
- 'pw_domain' => 'domain',
- 'pw_passwd' => 'crypt_password',
- 'pw_uid' => 'uid',
- 'pw_gid' => 'gid',
- 'pw_gecos' => 'finger',
- 'pw_dir' => 'dir',
- #'pw_shell' => 'shell',
- 'pw_shell' => 'quota',
-;
-my $vpopmail_map = join('\n', map "$_ $vpopmail_map{$_}", keys %vpopmail_map );
-
-tie my %postfix_courierimap_mailbox_map, 'Tie::IxHash',
- 'username' => 'email',
- 'password' => '_password',
- 'crypt' => 'crypt_password',
- 'name' => 'finger',
- 'maildir' => 'virtual_maildir',
- 'domain' => 'domain',
- 'svcnum' => 'svcnum',
-;
-my $postfix_courierimap_mailbox_map =
- join('\n', map "$_ $postfix_courierimap_mailbox_map{$_}",
- keys %postfix_courierimap_mailbox_map );
-
-tie my %postfix_courierimap_alias_map, 'Tie::IxHash',
- 'address' => 'email',
- 'goto' => 'email',
- 'domain' => 'domain',
- 'svcnum' => 'svcnum',
-;
-my $postfix_courierimap_alias_map =
- join('\n', map "$_ $postfix_courierimap_alias_map{$_}",
- keys %postfix_courierimap_alias_map );
-
-tie my %postfix_native_mailbox_map, 'Tie::IxHash',
- 'userid' => 'email',
- 'uid' => 'uid',
- 'gid' => 'gid',
- 'password' => 'ldap_password',
- 'mail' => 'domain_slash_username',
-;
-my $postfix_native_mailbox_map =
- join('\n', map "$_ $postfix_native_mailbox_map{$_}",
- keys %postfix_native_mailbox_map );
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export of accounts to SQL databases '.
- '(vpopmail, Postfix+Courier IMAP, others?)',
- 'options' => \%options,
- 'nodomain' => '',
- 'notes' => <<END
-Export accounts (svc_acct records) to SQL databases. Currently has default
-configurations for vpopmail and Postfix+Courier IMAP but intended to be
-configurable for other schemas as well.
-
-<BR><BR>In contrast to sqlmail, this is intended to export just svc_acct
-records only, rather than a single export for svc_acct, svc_forward and
-svc_domain records, to export in "default" database schemas rather than
-configure the MTA or POP/IMAP server for a Freeside-specific schema, and
-to be configured for different mail server setups.
-
-<BR><BR>Use these buttons for some useful presets:
-<UL>
- <li><INPUT TYPE="button" VALUE="vpopmail" onClick='
- this.form.table.value = "vpopmail";
- this.form.schema.value = "$vpopmail_map";
- this.form.primary_key.value = "pw_name, pw_domain";
- '>
- <LI><INPUT TYPE="button" VALUE="postfix_courierimap_mailbox" onClick='
- this.form.table.value = "mailbox";
- this.form.schema.value = "$postfix_courierimap_mailbox_map";
- this.form.primary_key.value = "username";
- '>
- <LI><INPUT TYPE="button" VALUE="postfix_courierimap_alias" onClick='
- this.form.table.value = "alias";
- this.form.schema.value = "$postfix_courierimap_alias_map";
- this.form.primary_key.value = "address";
- '>
- <LI><INPUT TYPE="button" VALUE="postfix_native_mailbox" onClick='
- this.form.table.value = "users";
- this.form.schema.value = "$postfix_native_mailbox_map";
- this.form.primary_key.value = "userid";
- '>
-</UL>
-END
-);
-
-sub _schema_map { shift->_map('schema'); }
-sub _static_map { shift->_map('static'); }
-
-sub _map {
- my $self = shift;
- map { /^\s*(\S+)\s*(\S+)\s*$/ } split("\n", $self->option(shift) );
-}
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self, $svc_acct) = (shift, shift);
-
- my %schema = $self->_schema_map;
- my %static = $self->_static_map;
-
- my %record = (
-
- ( map { $_ => $static{$_} } keys %static ),
-
- ( map { my $value = $schema{$_};
- my @arg = ();
- push @arg, $self->option('crypt')
- if $value eq 'crypt_password' && $self->option('crypt');
- $_ => $svc_acct->$value(@arg);
- } keys %schema
- ),
-
- );
-
- my $err_or_queue =
- $self->acct_sql_queue(
- $svc_acct->svcnum,
- 'insert',
- $self->option('table'),
- %record
- );
- return $err_or_queue unless ref($err_or_queue);
-
- '';
-
-}
-
-sub _export_replace {
- my($self, $new, $old) = (shift, shift, shift);
-
- my %schema = $self->_schema_map;
- my %static = $self->_static_map;
-
- my @primary_key = ();
- if ( $self->option('primary_key') =~ /,/ ) {
- foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) {
- my $keymap = $schema{$key};
- push @primary_key, $old->$keymap();
- }
- } else {
- my $keymap = $schema{$self->option('primary_key')};
- push @primary_key, $old->$keymap();
- }
-
- my %record = (
-
- ( map { $_ => $static{$_} } keys %static ),
-
- ( map { my $value = $schema{$_};
- my @arg = ();
- push @arg, $self->option('crypt')
- if $value eq 'crypt_password' && $self->option('crypt');
- $_ => $new->$value(@arg);
- } keys %schema
- ),
-
- );
-
- my $err_or_queue = $self->acct_sql_queue(
- $new->svcnum,
- 'replace',
- $self->option('table'),
- $self->option('primary_key'), @primary_key,
- %record,
- );
- return $err_or_queue unless ref($err_or_queue);
- '';
-}
-
-sub _export_delete {
- my ( $self, $svc_acct ) = (shift, shift);
-
- my %schema = $self->_schema_map;
-
- my %primary_key = ();
- if ( $self->option('primary_key') =~ /,/ ) {
- foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) {
- my $keymap = $schema{$key};
- $primary_key{ $key } = $svc_acct->$keymap();
- }
- } else {
- my $keymap = $schema{$self->option('primary_key')};
- $primary_key{ $self->option('primary_key') } = $svc_acct->$keymap(),
- }
-
- my $err_or_queue = $self->acct_sql_queue(
- $svc_acct->svcnum,
- 'delete',
- $self->option('table'),
- %primary_key,
- #$self->option('primary_key') => $svc_acct->$keymap(),
- );
- return $err_or_queue unless ref($err_or_queue);
- '';
-}
-
-sub acct_sql_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::acct_sql::acct_sql_$method",
- };
- $queue->insert(
- $self->option('datasrc'),
- $self->option('username'),
- $self->option('password'),
- @_,
- ) or $queue;
-}
-
-sub acct_sql_insert { #subroutine, not method
- my $dbh = acct_sql_connect(shift, shift, shift);
- my( $table, %record ) = @_;
-
- my $sth = $dbh->prepare(
- "INSERT INTO $table ( ". join(", ", keys %record).
- " ) VALUES ( ". join(", ", map '?', keys %record ). " )"
- ) or die $dbh->errstr;
-
- $sth->execute( values(%record) )
- or die "can't insert into $table table: ". $sth->errstr;
-
- $dbh->disconnect;
-}
-
-sub acct_sql_delete { #subroutine, not method
- my $dbh = acct_sql_connect(shift, shift, shift);
- my( $table, %record ) = @_;
-
- my $sth = $dbh->prepare(
- "DELETE FROM $table WHERE ". join(' AND ', map "$_ = ? ", keys %record )
- ) or die $dbh->errstr;
-
- $sth->execute( map $record{$_}, keys %record )
- or die "can't delete from $table table: ". $sth->errstr;
-
- $dbh->disconnect;
-}
-
-sub acct_sql_replace { #subroutine, not method
- my $dbh = acct_sql_connect(shift, shift, shift);
-
- my( $table, $pkey ) = ( shift, shift );
-
- my %primary_key = ();
- if ( $pkey =~ /,/ ) {
- foreach my $key ( split(/\s*,\s*/, $pkey ) ) {
- $primary_key{$key} = shift;
- }
- } else {
- $primary_key{$pkey} = shift;
- }
-
- my %record = @_;
-
- my $sth = $dbh->prepare(
- "UPDATE $table".
- ' SET '. join(', ', map "$_ = ?", keys %record ).
- ' WHERE '. join(' AND ', map "$_ = ?", keys %primary_key )
- ) or die $dbh->errstr;
-
- $sth->execute( values(%record), values(%primary_key) );
-
- $dbh->disconnect;
-}
-
-sub acct_sql_connect {
- #my($datasrc, $username, $password) = @_;
- #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
- DBI->connect(@_) or die $DBI::errstr;
-}
-
-1;
-
diff --git a/FS/FS/part_export/apache.pm b/FS/FS/part_export/apache.pm
deleted file mode 100644
index 35b00cc..0000000
--- a/FS/FS/part_export/apache.pm
+++ /dev/null
@@ -1,47 +0,0 @@
-package FS::part_export::apache;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export::null;
-
-@ISA = qw(FS::part_export::null);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote username', default=>'root' },
- 'httpd_conf' => { label=>'httpd.conf snippet location',
- default=>'/etc/apache/httpd-freeside.conf', },
- 'restart' => { label=>'Apache restart command',
- default=>'apachectl graceful',
- },
- 'template' => {
- label => 'Template',
- type => 'textarea',
- default => <<'END',
-<VirtualHost $domain> #generic
-#<VirtualHost ip.addr> #preferred, http://httpd.apache.org/docs/dns-caveats.html
-DocumentRoot /var/www/$zone
-ServerName $zone
-ServerAlias *.$zone
-#BandWidthModule On
-#LargeFileLimit 4096 12288
-#FrontpageEnable on
-</VirtualHost>
-
-END
- },
-;
-
-%info = (
- 'svc' => 'svc_www',
- 'desc' => 'Export an Apache httpd.conf file snippet.',
- 'options' => \%options,
- 'notes' => <<'END'
-Batch export of an httpd.conf snippet from a template. Typically used with
-something like <code>Include /etc/apache/httpd-freeside.conf</code> in
-httpd.conf. <a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
-must be installed. Run bin/apache.export to export the files.
-END
-);
-
-1;
-
diff --git a/FS/FS/part_export/artera_turbo.pm b/FS/FS/part_export/artera_turbo.pm
deleted file mode 100644
index c006db9..0000000
--- a/FS/FS/part_export/artera_turbo.pm
+++ /dev/null
@@ -1,181 +0,0 @@
-package FS::part_export::artera_turbo;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::Record qw(qsearch);
-use FS::part_export;
-use FS::cust_svc;
-use FS::svc_external;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'rid' => { 'label' => 'Reseller ID (RID)' },
- 'username' => { 'label' => 'Reseller username', },
- 'password' => { 'label' => 'Reseller password', },
- 'pid' => { 'label' => 'Artera Product ID', },
- 'priceid' => { 'label' => 'Artera Price ID', },
- 'agent_aid' => { 'label' => 'Export agentnum values to Artera AID',
- 'type' => 'checkbox',
- },
- 'aid' => { 'label' => 'Artera Agent ID to use if not using agentnum values', },
- 'production' => { 'label' => 'Production mode (leave unchecked for staging)',
- 'type' => 'checkbox',
- },
- 'debug' => { 'label' => 'Enable debug logging',
- 'type' => 'checkbox',
- },
- 'enable_edit' => { 'label' => 'Enable local editing of Artera serial numbers and key codes (note that the changes will NOT be exported to Artera)',
- 'type' => 'checkbox',
- },
-;
-
-%info = (
- 'svc' => 'svc_external',
- #'svc' => [qw( svc_acct svc_forward )],
- 'desc' =>
- 'Real-time export to Artera Turbo Reseller API',
- 'options' => \%options,
- #'nodomain' => 'Y',
- 'notes' => <<'END'
-Real-time export to <a href="http://www.arteraturbo.com/">Artera Turbo</a>
-Reseller API. Requires installation of
-<a href="http://search.cpan.org/dist/Net-Artera">Net::Artera</a>
-from CPAN. You probably also want to:
-<UL>
- <LI>In the configuration UI section: set the <B>svc_external-skip_manual</B> and <B>svc_external-display_type</B> configuration values.
- <LI>In the message catalog: set <B>svc_external-id</B> to <I>Artera Serial Number</I> and set <B>svc_external-title</B> to <I>Artera Key Code</I>.
-</UL>
-END
-);
-
-sub rebless { shift; }
-
-sub _new_Artera {
- my $self = shift;
-
- my $artera = new Net::Artera (
- map { $_ => $self->option($_) }
- qw( rid username password production )
- );
-}
-
-
-sub _export_insert {
- my($self, $svc_external) = (shift, shift);
-
- # want the ASN (serial) and AKC (key code) right away
-
- eval "use Net::Artera;";
- return $@ if $@;
- $Net::Artera::DEBUG = 1 if $self->option('debug');
- my $artera = $self->_new_Artera;
-
- my $cust_pkg = $svc_external->cust_svc->cust_pkg;
- my $part_pkg = $cust_pkg->part_pkg;
- my @svc_acct = grep { $_->table eq 'svc_acct' }
- map { $_->svc_x }
- sort { my $svcpart = $part_pkg->svcpart('svc_acct');
- ($b->svcpart==$svcpart) cmp ($a->svcpart==$svcpart); }
- qsearch('cust_svc', { 'pkgnum' => $cust_pkg->pkgnum } );
- my $email = scalar(@svc_acct) ? $svc_acct[0]->email : '';
-
- my $cust_main = $cust_pkg->cust_main;
-
- my $result = $artera->newOrder(
- 'pid' => $self->option('pid'),
- 'priceid' => $self->option('priceid'),
- 'email' => $email,
- 'cname' => $cust_main->name,
- 'ref' => $svc_external->svcnum,
- 'aid' => ( $self->option('agent_aid')
- ? $cust_main->agentnum
- : $self->option('aid') ),
- 'add1' => $cust_main->address1,
- 'add2' => $cust_main->address2,
- 'add3' => $cust_main->city,
- 'add4' => $cust_main->state,
- 'zip' => $cust_main->zip,
- 'cid' => $cust_main->country,
- 'phone' => $cust_main->daytime || $cust_main->night,
- 'fax' => $cust_main->fax,
- );
-
- if ( $result->{'id'} == 1 ) {
- my $new = new FS::svc_external { $svc_external->hash };
- $new->id(sprintf('%010d', $result->{'ASN'}));
- $new->title( substr('0000000000'.uc($result->{'AKC'}), -10) );
- $new->replace($svc_external);
- } else {
- $result->{'message'} || 'No response from Artera';
- }
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
- return '' if $self->option('enable_edit');
- return "can't change serial number with Artera"
- if $old->id != $new->id && $old->id;
- return "can't change key code with Artera"
- if $old->title ne $new->title && $old->title;
- '';
-}
-
-sub _export_delete {
- my( $self, $svc_external ) = (shift, shift);
- $self->queue_statusChange(17, $svc_external);
-}
-
-sub _export_suspend {
- my( $self, $svc_external ) = (shift, shift);
- $self->queue_statusChange(16, $svc_external);
-}
-
-sub _export_unsuspend {
- my( $self, $svc_external ) = (shift, shift);
- $self->queue_statusChange(15, $svc_external);
-}
-
-sub queue_statusChange {
- my( $self, $status, $svc_external ) = @_;
-
- my $queue = new FS::queue {
- 'svcnum' => $svc_external->svcnum,
- 'job' => 'FS::part_export::artera_turbo::statusChange',
- };
- $queue->insert(
- ( map { $self->option($_) }
- qw( rid username password production ) ),
- $status,
- $svc_external->id,
- $svc_external->title,
- $self->option('debug'),
- );
-}
-
-sub statusChange {
- my( $rid, $username, $password, $prod, $status, $id, $title, $debug ) = @_;
-
- eval "use Net::Artera;";
- return $@ if $@;
- $Net::Artera::DEBUG = 1 if $debug;
-
- my $artera = new Net::Artera (
- 'rid' => $rid,
- 'username' => $username,
- 'password' => $password,
- 'production' => $prod,
- );
-
- my $result = $artera->statusChange(
- 'asn' => sprintf('%010d', $id),
- 'akc' => substr("0000000000$title", -10),
- 'statusid' => $status,
- );
-
- die $result->{'message'} unless $result->{'id'} == 1;
-
-}
-
-1;
-
diff --git a/FS/FS/part_export/bind.pm b/FS/FS/part_export/bind.pm
deleted file mode 100644
index 1ef7b65..0000000
--- a/FS/FS/part_export/bind.pm
+++ /dev/null
@@ -1,35 +0,0 @@
-package FS::part_export::bind;
-
-use vars qw(@ISA %info %options);
-use Tie::IxHash;
-use FS::part_export::null;
-
-@ISA = qw(FS::part_export::null);
-
-tie %options, 'Tie::IxHash',
- 'named_conf' => { label => 'named.conf location',
- default=> '/etc/bind/named.conf' },
- 'zonepath' => { label => 'path to zone files',
- default=> '/etc/bind/', },
- 'bind_release' => { label => 'ISC BIND Release',
- type => 'select',
- options => [qw(BIND8 BIND9)],
- default => 'BIND8' },
- 'bind9_minttl' => { label => 'The minttl required by bind9 and RFC1035.',
- default => '1D' },
- 'reload' => { label => 'Optional reload command. If not specified, defaults to "ndc" under BIND8 and "rndc" under BIND9.', },
-;
-
-%info = (
- 'svc' => 'svc_domain',
- 'desc' => 'Batch export to BIND named',
- 'options' => \%options,
- 'notes' => <<'END'
-Batch export of BIND zone and configuration files to a primary nameserver.
-<a href="http://search.cpan.org/search?dist=File-Rsync">File::Rsync</a>
-must be installed. Run bin/bind.export to export the files.
-END
-);
-
-1;
-
diff --git a/FS/FS/part_export/bind_slave.pm b/FS/FS/part_export/bind_slave.pm
deleted file mode 100644
index c89325f..0000000
--- a/FS/FS/part_export/bind_slave.pm
+++ /dev/null
@@ -1,28 +0,0 @@
-package FS::part_export::bind_slave;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export::null;
-
-@ISA = qw(FS::part_export::null);
-
-tie my %options, 'Tie::IxHash',
- 'master' => { label=> 'Master IP address(s) (semicolon-separated)' },
- %FS::part_export::bind::options,
-;
-delete $options{'zonepath'};
-
-%info = (
- 'svc' => 'svc_domain',
- 'desc' =>'Batch export to slave BIND named',
- 'options' => \%options,
- 'notes' => <<'END'
-Batch export of BIND configuration file to a secondary nameserver. Zones are
-slaved from the listed masters.
-<a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
-must be installed. Run bin/bind.export to export the files.
-END
-);
-
-1;
-
diff --git a/FS/FS/part_export/bsdshell.pm b/FS/FS/part_export/bsdshell.pm
deleted file mode 100644
index 7b5feb2..0000000
--- a/FS/FS/part_export/bsdshell.pm
+++ /dev/null
@@ -1,25 +0,0 @@
-package FS::part_export::bsdshell;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export::passwdfile;
-
-@ISA = qw(FS::part_export::passwdfile);
-
-tie my %options, 'Tie::IxHash', %FS::part_export::passwdfile::options;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' =>
- 'Batch export of /etc/passwd and /etc/master.passwd files (BSD)',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => <<'END'
-MD5 crypt requires installation of
-<a href="http://search.cpan.org/dist/Crypt-PasswdMD5">Crypt::PasswdMD5</a>
-from CPAN. Run bin/bsdshell.export to export the files.
-END
-);
-
-1;
-
diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm
deleted file mode 100644
index ecb3780..0000000
--- a/FS/FS/part_export/communigate_pro.pm
+++ /dev/null
@@ -1,178 +0,0 @@
-package FS::part_export::communigate_pro;
-
-use vars qw(@ISA %info %options);
-use Tie::IxHash;
-use FS::part_export;
-use FS::queue;
-
-@ISA = qw(FS::part_export);
-
-tie %options, 'Tie::IxHash',
- 'port' => { label=>'Port number', default=>'106', },
- 'login' => { label=>'The administrator account name. The name can contain a domain part.', },
- 'password' => { label=>'The administrator account password.', },
- 'accountType' => { label=>'Type for newly-created accounts',
- type=>'select',
- options=>[qw( MultiMailbox TextMailbox MailDirMailbox )],
- default=>'MultiMailbox',
- },
- 'externalFlag' => { label=> 'Create accounts with an external (visible for legacy mailers) INBOX.',
- type=>'checkbox',
- },
- 'AccessModes' => { label=>'Access modes',
- default=>'Mail POP IMAP PWD WebMail WebSite',
- },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to a CommuniGate Pro mail server',
- 'options' => \%options,
- 'notes' => <<'END'
-Real time export to a
-<a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a>
-mail server. The
-<a href="http://www.stalker.com/CGPerl/">CommuniGate Pro Perl Interface</a>
-must be installed as CGP::CLI.
-END
-);
-
-sub rebless { shift; }
-
-sub export_username {
- my($self, $svc_acct) = (shift, shift);
- $svc_acct->email;
-}
-
-sub _export_insert {
- my( $self, $svc_acct ) = (shift, shift);
- my @options = ( $svc_acct->svcnum, 'CreateAccount',
- 'accountName' => $self->export_username($svc_acct),
- 'accountType' => $self->option('accountType'),
- 'AccessModes' => $self->option('AccessModes'),
- 'RealName' => $svc_acct->finger,
- 'Password' => $svc_acct->_password,
- );
- push @options, 'MaxAccountSize' => $svc_acct->quota if $svc_acct->quota;
- push @options, 'externalFlag' => $self->option('externalFlag')
- if $self->option('externalFlag');
-
- $self->communigate_pro_queue( @options );
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
- return "can't (yet) change username with CommuniGate Pro"
- if $old->username ne $new->username;
- return "can't (yet) change domain with CommuniGate Pro"
- if $self->export_username($old) ne $self->export_username($new);
- return "can't (yet) change GECOS with CommuniGate Pro"
- if $old->finger ne $new->finger;
- return "can't (yet) change quota with CommuniGate Pro"
- if $old->quota ne $new->quota;
- return '' unless $old->username ne $new->username
- || $old->_password ne $new->_password
- || $old->finger ne $new->finger
- || $old->quota ne $new->quota;
-
- return '' if '*SUSPENDED* '. $old->_password eq $new->_password;
-
- #my $err_or_queue = $self->communigate_pro_queue( $new->svcnum,'RenameAccount',
- # $old->email, $new->email );
- #return $err_or_queue unless ref($err_or_queue);
- #my $jobnum = $err_or_queue->jobnum;
-
- $self->communigate_pro_queue( $new->svcnum, 'SetAccountPassword',
- $self->export_username($new), $new->_password )
- if $new->_password ne $old->_password;
-
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- $self->communigate_pro_queue( $svc_acct->svcnum, 'DeleteAccount',
- $self->export_username($svc_acct),
- );
-}
-
-sub _export_suspend {
- my( $self, $svc_acct ) = (shift, shift);
- $self->communigate_pro_queue( $svc_acct->svcnum, 'UpdateAccountSettings',
- 'accountName' => $self->export_username($svc_acct),
- 'AccessModes' => 'Mail',
- );
-}
-
-sub _export_unsuspend {
- my( $self, $svc_acct ) = (shift, shift);
- $self->communigate_pro_queue( $svc_acct->svcnum, 'UpdateAccountSettings',
- 'accountName' => $self->export_username($svc_acct),
- 'AccessModes' => $self->option('AccessModes'),
- );
-}
-
-sub communigate_pro_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my @kludge_methods = qw(CreateAccount UpdateAccountSettings);
- my $sub = 'communigate_pro_command';
- $sub = $method if grep { $method eq $_ } @kludge_methods;
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::communigate_pro::$sub",
- };
- $queue->insert(
- $self->machine,
- $self->option('port'),
- $self->option('login'),
- $self->option('password'),
- $method,
- @_,
- );
-
-}
-
-sub CreateAccount {
- my( $machine, $port, $login, $password, $method, %args ) = @_;
- my $accountName = delete $args{'accountName'};
- my $accountType = delete $args{'accountType'};
- my $externalFlag = delete $args{'externalFlag'};
- $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
- my @args = ( accountName => $accountName,
- accountType => $accountType,
- settings => \%args,
- );
- #externalFlag => $externalFlag,
- push @args, externalFlag => $externalFlag if $externalFlag;
-
- communigate_pro_command( $machine, $port, $login, $password, $method, @args );
-
-}
-
-sub UpdateAccountSettings {
- my( $machine, $port, $login, $password, $method, %args ) = @_;
- my $accountName = delete $args{'accountName'};
- $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
- @args = ( $accountName, \%args );
- communigate_pro_command( $machine, $port, $login, $password, $method, @args );
-}
-
-sub communigate_pro_command { #subroutine, not method
- my( $machine, $port, $login, $password, $method, @args ) = @_;
-
- eval "use CGP::CLI";
-
- my $cli = new CGP::CLI( {
- 'PeerAddr' => $machine,
- 'PeerPort' => $port,
- 'login' => $login,
- 'password' => $password,
- } ) or die "Can't login to CGPro: $CGP::ERR_STRING\n";
-
- $cli->$method(@args) or die "CGPro error: ". $cli->getErrMessage;
-
- $cli->Logout; # or die "Can't logout of CGPro: $CGP::ERR_STRING\n";
-
-}
-
-1;
-
diff --git a/FS/FS/part_export/communigate_pro_singledomain.pm b/FS/FS/part_export/communigate_pro_singledomain.pm
deleted file mode 100644
index e25043f..0000000
--- a/FS/FS/part_export/communigate_pro_singledomain.pm
+++ /dev/null
@@ -1,37 +0,0 @@
-package FS::part_export::communigate_pro_singledomain;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export::communigate_pro;
-
-@ISA = qw(FS::part_export::communigate_pro);
-
-tie my %options, 'Tie::IxHash', %FS::part_export::communigate_pro::options,
- 'domain' => { label=>'Domain', },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' =>
- 'Real-time export to a CommuniGate Pro mail server, one domain only',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => <<'END'
-Real time export to a
-<a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a>
-mail server. This is an unusual export to CommuniGate Pro that forces all
-accounts into a single domain. As CommuniGate Pro supports multiple domains,
-unless you have a specific reason for using this export, you probably want to
-use the communigate_pro export instead. The
-<a href="http://www.stalker.com/CGPerl/">CommuniGate Pro Perl Interface</a>
-must be installed as CGP::CLI.
-END
-);
-
-sub export_username {
- my($self, $svc_acct) = (shift, shift);
- $svc_acct->username. '@'. $self->option('domain');
-}
-
-1;
-
diff --git a/FS/FS/part_export/cp.pm b/FS/FS/part_export/cp.pm
deleted file mode 100644
index 96fa437..0000000
--- a/FS/FS/part_export/cp.pm
+++ /dev/null
@@ -1,161 +0,0 @@
-package FS::part_export::cp;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'port' => { label=>'Port number' },
- 'username' => { label=>'Username' },
- 'password' => { label=>'Password' },
- 'domain' => { label=>'Domain' },
- 'workgroup' => { label=>'Default Workgroup' },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to Critical Path Account Provisioning Protocol',
- 'options'=> \%options,
- 'notes' => <<'END'
-Real-time export to
-<a href="http://www.cp.net/">Critial Path Account Provisioning Protocol</a>.
-Requires installation of
-<a href="http://search.cpan.org/dist/Net-APP">Net::APP</a>
-from CPAN.
-END
-);
-
-sub rebless { shift; }
-
-sub _export_insert {
- my( $self, $svc_acct ) = (shift, shift);
- $self->cp_queue( $svc_acct->svcnum, 'create_mailbox',
- 'Mailbox' => $svc_acct->username,
- 'Password' => $svc_acct->_password,
- 'Workgroup' => $self->option('workgroup'),
- 'Domain' => $svc_acct->domain,
- );
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
- return "can't change domain with Critical Path"
- if $old->domain ne $new->domain;
- return "can't change username with Critical Path" #CP no longer supports this
- if $old->username ne $new->username;
- return '' unless $old->_password ne $new->_password;
- $self->cp_queue( $new->svcnum, 'replace', $new->domain,
- $old->username, $new->username, $old->_password, $new->_password );
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- $self->cp_queue( $svc_acct->svcnum, 'delete_mailbox',
- 'Mailbox' => $svc_acct->username,
- 'Domain' => $svc_acct->domain,
- );
-}
-
-sub _export_suspend {
- my( $self, $svc_acct ) = (shift, shift);
- $self->cp_queue( $svc_acct->svcnum, 'set_mailbox_status',
- 'Mailbox' => $svc_acct->username,
- 'Domain' => $svc_acct->domain,
- 'OTHER' => 'T',
- 'OTHER_SUSPEND' => 'T',
- );
-}
-
-sub _export_unsuspend {
- my( $self, $svc_acct ) = (shift, shift);
- $self->cp_queue( $svc_acct->svcnum, 'set_mailbox_status',
- 'Mailbox' => $svc_acct->username,
- 'Domain' => $svc_acct->domain,
- 'PAYMENT' => 'F',
- 'OTHER' => 'F',
- 'OTHER_SUSPEND' => 'F',
- 'OTHER_BOUNCE' => 'F',
- );
-}
-
-sub cp_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => 'FS::part_export::cp::cp_command',
- };
- $queue->insert(
- $self->machine,
- $self->option('port'),
- $self->option('username'),
- $self->option('password'),
- $self->option('domain'),
- $method,
- @_,
- );
-}
-
-sub cp_command { #subroutine, not method
- my($host, $port, $username, $password, $login_domain, $method, @args) = @_;
-
- #quelle hack
- if ( $method eq 'replace' ) {
-
- my( $domain, $old_username, $new_username, $old_password, $new_password)
- = @args;
-
- if ( $old_username ne $new_username ) {
- cp_command($host, $port, $username, $password, 'rename_mailbox',
- Domain => $domain,
- Old_Mailbox => $old_username,
- New_Mailbox => $new_username,
- );
- }
-
- #my $other = 'F';
- if ( $new_password =~ /^\*SUSPENDED\* (.*)$/ ) {
- $new_password = $1;
- # $other = 'T';
- }
- #cp_command($host, $port, $username, $password, $login_domain,
- # 'set_mailbox_status',
- # Domain => $domain,
- # Mailbox => $new_username,
- # Other => $other,
- # Other_Bounce => $other,
- #);
-
- if ( $old_password ne $new_password ) {
- cp_command($host, $port, $username, $password, $login_domain,
- 'change_mailbox',
- Domain => $domain,
- Mailbox => $new_username,
- Password => $new_password,
- );
- }
-
- return;
- }
- #eof quelle hack
-
- eval "use Net::APP;";
-
- my $app = new Net::APP (
- "$host:$port",
- User => $username,
- Password => $password,
- Domain => $login_domain,
- Timeout => 60,
- #Debug => 1,
- ) or die "$@\n";
-
- $app->$method( @args );
-
- die $app->message."\n" unless $app->ok;
-
-}
-
-1;
-
diff --git a/FS/FS/part_export/cpanel.pm b/FS/FS/part_export/cpanel.pm
deleted file mode 100644
index 0ad00df..0000000
--- a/FS/FS/part_export/cpanel.pm
+++ /dev/null
@@ -1,192 +0,0 @@
-package FS::part_export::cpanel;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote access username' },
- 'accesshash' => { label=>'Remote access key', type=>'textarea' },
- 'debug' => { label=>'Enable debugging', type=>'checkbox' },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to Cpanel control panel.',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => 'Real time export to a the <a href="http://www.cpanel.net/">Cpanel</a> control panel software. Service definition names are exported as Cpanel packages. Requires installation of the Cpanel::Accounting perl module distributed with Cpanel.',
-);
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self, $svc_acct) = (shift, shift);
- $err_or_queue = $self->cpanel_queue( $svc_acct->svcnum, 'insert',
- $svc_acct->domain,
- $svc_acct->username,
- $svc_acct->_password,
- $svc_acct->cust_svc->part_svc->svc,
- );
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
- return "can't change username with cpanel"
- if $old->username ne $new->username;
- return "can't change password with cpanel"
- if $old->_passsword ne $new->_password;
- return "can't change domain with cpanel"
- if $old->domain ne $new->domain;
-
- '';
-
- ##return '' unless $old->_password ne $new->_password;
- #$err_or_queue = $self->cpanel_queue( $new->svcnum,
- # 'replace', $new->username, $new->_password );
- #ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- $err_or_queue = $self->cpanel_queue( $svc_acct->svcnum,
- 'delete', $svc_acct->username
- );
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-sub _export_suspend {
- my( $self, $svc_acct ) = (shift, shift);
- $err_or_queue = $self->cpanel_queue( $svc_acct->svcnum,
- 'suspend', $svc_acct->username );
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-sub _export_unsuspend {
- my( $self, $svc_acct ) = (shift, shift);
- $err_or_queue = $self->cpanel_queue( $svc_acct->svcnum,
- 'unsuspend', $svc_acct->username );
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-
-sub cpanel_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::cpanel::cpanel_$method",
- };
- $queue->insert(
- $self->machine,
- $self->option('user'),
- $self->option('accesshash'),
- $self->option('debug'),
- @_
- ) or $queue;
-}
-
-
-sub cpanel_insert { #subroutine, not method
- my( $machine, $user, $accesshash, $debug ) = splice(@_,0,4);
-
-# my $whm = cpanel_connect($machine, $user, $accesshash, $debug);
-# warn " cpanel->createacct ". join(', ', @_). "\n"
-# if $debug;
-# my $response = $whm->createacct(@_);
-# die $whm->{'error'} if $whm->{'error'};
-# warn " cpanel response: $response\n"
-# if $debug;
-
- warn "cpanel_insert: attempting web interface to add POP"
- if $debug;
-
- my($domain, $username, $password, $svc) = @_;
-
- use LWP::UserAgent;
- use HTTP::Request::Common qw(POST);
-
- my $url =
- "http://$user:$accesshash\@$domain:2082/frontend/x/mail/addpop2.html";
-
- my $ua = LWP::UserAgent->new();
-
- #$req->authorization_basic($user, $accesshash);
-
- my $res = $ua->request(
- POST( $url,
- [
- 'email' => $username,
- 'domain' => $domain,
- 'password' => $password,
- 'quota' => 10, #?
- ]
- )
- );
-
- die "Error submitting data to $url: ". $res->status_line
- unless $res->is_success;
-
- die "Username in use"
- if $res->content =~ /exists/;
-
- die "Account not created: ". $res->content
- if $res->content =~ /failure/;
-
-}
-
-#sub cpanel_replace { #subroutine, not method
-#}
-
-sub cpanel_delete { #subroutine, not method
- my( $machine, $user, $accesshash, $debug ) = splice(@_,0,4);
- my $whm = cpanel_connect($machine, $user, $accesshash, $debug);
- warn " cpanel->killacct ". join(', ', @_). "\n"
- if $debug;
- my $response = $whm->killacct(shift);
- die $whm->{'error'} if $whm->{'error'};
- warn " cpanel response: $response\n"
- if $debug;
-}
-
-sub cpanel_suspend { #subroutine, not method
- my( $machine, $user, $accesshash, $debug ) = splice(@_,0,4);
- my $whm = cpanel_connect($machine, $user, $accesshash, $debug);
- warn " cpanel->suspend ". join(', ', @_). "\n"
- if $debug;
- my $response = $whm->suspend(shift);
- die $whm->{'error'} if $whm->{'error'};
- warn " cpanel response: $response\n"
- if $debug;
-}
-
-sub cpanel_unsuspend { #subroutine, not method
- my( $machine, $user, $accesshash, $debug ) = splice(@_,0,4);
- my $whm = cpanel_connect($machine, $user, $accesshash, $debug);
- warn " cpanel->unsuspend ". join(', ', @_). "\n"
- if $debug;
- my $response = $whm->unsuspend(shift);
- die $whm->{'error'} if $whm->{'error'};
- warn " cpanel response: $response\n"
- if $debug;
-}
-
-sub cpanel_connect {
- my( $host, $user, $accesshash, $debug ) = @_;
-
- eval "use Cpanel::Accounting;";
- die $@ if $@;
-
- warn "creating new Cpanel::Accounting connection to $user@$host\n"
- if $debug;
-
- my $whm = new Cpanel::Accounting;
- $whm->{'host'} = $host;
- $whm->{'user'} = $user;
- $whm->{'accesshash'} = $accesshash;
- $whm->{'usessl'} = 1;
-
- $whm;
-}
diff --git a/FS/FS/part_export/cyrus.pm b/FS/FS/part_export/cyrus.pm
deleted file mode 100644
index 84c9e5a..0000000
--- a/FS/FS/part_export/cyrus.pm
+++ /dev/null
@@ -1,120 +0,0 @@
-package FS::part_export::cyrus;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'server' => { label=>'IMAP server' },
- 'username' => { label=>'Admin username' },
- 'password' => { label=>'Admin password' },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to Cyrus IMAP server',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => <<'END'
-Integration with
-<a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a>.
-Cyrus::IMAP::Admin should be installed locally and the connection to the
-server secured. <B>svc_acct.quota</B>, if available, is used to set the
-Cyrus quota.
-END
-);
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self, $svc_acct) = (shift, shift);
- $self->cyrus_queue( $svc_acct->svcnum, 'insert',
- $svc_acct->username, $svc_acct->quota );
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
- return "can't change username using Cyrus"
- if $old->username ne $new->username;
- return '';
-# #return '' unless $old->_password ne $new->_password;
-# $self->cyrus_queue( $new->svcnum,
-# 'replace', $new->username, $new->_password );
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- $self->cyrus_queue( $svc_acct->svcnum, 'delete',
- $svc_acct->username );
-}
-
-#a good idea to queue anything that could fail or take any time
-sub cyrus_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::cyrus::cyrus_$method",
- };
- $queue->insert(
- $self->option('server'),
- $self->option('username'),
- $self->option('password'),
- @_
- );
-}
-
-sub cyrus_insert { #subroutine, not method
- my $client = cyrus_connect(shift, shift, shift);
- my( $username, $quota ) = @_;
- my $rc = $client->create("user.$username");
- my $error = $client->error;
- die "creating user.$username: $error" if $error;
-
- $rc = $client->setacl("user.$username", $username => 'all' );
- $error = $client->error;
- die "setacl user.$username: $error" if $error;
-
- if ( $quota ) {
- $rc = $client->setquota("user.$username", 'STORAGE' => $quota );
- $error = $client->error;
- die "setquota user.$username: $error" if $error;
- }
-
-}
-
-sub cyrus_delete { #subroutine, not method
- my ( $server, $admin_username, $password_username, $username ) = @_;
- my $client = cyrus_connect($server, $admin_username, $password_username);
-
- my $rc = $client->setacl("user.$username", $admin_username => 'all' );
- my $error = $client->error;
- die $error if $error;
-
- $rc = $client->delete("user.$username");
- $error = $client->error;
- die $error if $error;
-}
-
-sub cyrus_connect {
-
- my( $server, $admin_username, $admin_password ) = @_;
-
- eval "use Cyrus::IMAP::Admin;";
-
- my $client = Cyrus::IMAP::Admin->new($server);
- $client->authenticate(
- -user => $admin_username,
- -mechanism => "login",
- -password => $admin_password,
- );
- $client;
-
-}
-
-#sub cyrus_replace { #subroutine, not method
-#}
-
-1;
-
diff --git a/FS/FS/part_export/domain_shellcommands.pm b/FS/FS/part_export/domain_shellcommands.pm
deleted file mode 100644
index 994c113..0000000
--- a/FS/FS/part_export/domain_shellcommands.pm
+++ /dev/null
@@ -1,165 +0,0 @@
-package FS::part_export::domain_shellcommands;
-
-use strict;
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote username', default=>'root' },
- 'useradd' => { label=>'Insert command',
- default=>'',
- },
- 'userdel' => { label=>'Delete command',
- default=>'',
- },
- 'usermod' => { label=>'Modify command',
- default=>'',
- },
-;
-
-%info = (
- 'svc' => 'svc_domain',
- 'desc' => 'Run remote commands via SSH, for domains (qmail, ISPMan).',
- 'options' => \%options,
- 'notes' => <<'END'
-Run remote commands via SSH, for domains. You will need to
-<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
-<BR><BR>Use these buttons for some useful presets:
-<UL>
- <LI>
- <INPUT TYPE="button" VALUE="qmail catchall .qmail-domain-default maintenance" onClick='
- this.form.useradd.value = "[ \"$uid\" -a \"$gid\" -a \"$dir\" -a \"$qdomain\" ] && [ -e $dir/.qmail-$qdomain-default ] || { touch $dir/.qmail-$qdomain-default; chown $uid:$gid $dir/.qmail-$qdomain-default; }";
- this.form.userdel.value = "";
- this.form.usermod.value = "";
- '>
- <LI>
- <INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
- this.form.useradd.value = "/usr/local/ispman/bin/ispman.addDomain -d $domain changeme";
- this.form.userdel.value = "/usr/local/ispman/bin/ispman.deleteDomain -d $domain";
- this.form.usermod.value = "";
- '>
-</UL>
-The following variables are available for interpolation (prefixed with <code>new_</code> or <code>old_</code> for replace operations):
-<UL>
- <LI><code>$domain</code>
- <LI><code>$qdomain</code> - domain with periods replaced by colons
- <LI><code>$uid</code> - of catchall account
- <LI><code>$gid</code> - of catchall account
- <LI><code>$dir</code> - home directory of catchall account
- <LI>All other fields in
- <a href="../docs/schema.html#svc_domain">svc_domain</a> are also available.
-</UL>
-END
-);
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self) = shift;
- $self->_export_command('useradd', @_);
-}
-
-sub _export_delete {
- my($self) = shift;
- $self->_export_command('userdel', @_);
-}
-
-sub _export_command {
- my ( $self, $action, $svc_domain) = (shift, shift, shift);
- my $command = $self->option($action);
- return '' if $command =~ /^\s*$/;
-
- #set variable for the command
- no strict 'vars';
- {
- no strict 'refs';
- ${$_} = $svc_domain->getfield($_) foreach $svc_domain->fields;
- }
- ( $qdomain = $domain ) =~ s/\./:/g; #see dot-qmail(5): EXTENSION ADDRESSES
-
- if ( $svc_domain->catchall ) {
- no strict 'refs';
- my $svc_acct = $svc_domain->catchall_svc_acct;
- ${$_} = $svc_acct->getfield($_) foreach qw(uid gid dir);
- } else {
- no strict 'refs';
- ${$_} = '' foreach qw(uid gid dir);
- }
-
- #done setting variables for the command
-
- $self->shellcommands_queue( $svc_domain->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => eval(qq("$command")),
- );
-}
-
-sub _export_replace {
- my($self, $new, $old ) = (shift, shift, shift);
- my $command = $self->option('usermod');
-
- #set variable for the command
- no strict 'vars';
- {
- no strict 'refs';
- ${"old_$_"} = $old->getfield($_) foreach $old->fields;
- ${"new_$_"} = $new->getfield($_) foreach $new->fields;
- }
- ( $old_qdomain = $old_domain ) =~ s/\./:/g; #see dot-qmail(5): EXTENSION ADDRESSES
- ( $new_qdomain = $new_domain ) =~ s/\./:/g; #see dot-qmail(5): EXTENSION ADDRESSES
-
- {
- no strict 'refs';
-
- if ( $old->catchall ) {
- my $svc_acct = $old->catchall_svc_acct;
- ${"old_$_"} = $svc_acct->getfield($_) foreach qw(uid gid dir);
- } else {
- ${"old_$_"} = '' foreach qw(uid gid dir);
- }
- if ( $new->catchall ) {
- my $svc_acct = $new->catchall_svc_acct;
- ${"new_$_"} = $svc_acct->getfield($_) foreach qw(uid gid dir);
- } else {
- ${"new_$_"} = '' foreach qw(uid gid dir);
- }
-
- }
-
- #done setting variables for the command
-
- $self->shellcommands_queue( $new->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => eval(qq("$command")),
- );
-}
-
-#a good idea to queue anything that could fail or take any time
-sub shellcommands_queue {
- my( $self, $svcnum ) = (shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::domain_shellcommands::ssh_cmd",
- };
- $queue->insert( @_ );
-}
-
-sub ssh_cmd { #subroutine, not method
- use Net::SSH '0.08';
- &Net::SSH::ssh_cmd( { @_ } );
-}
-
-#sub shellcommands_insert { #subroutine, not method
-#}
-#sub shellcommands_replace { #subroutine, not method
-#}
-#sub shellcommands_delete { #subroutine, not method
-#}
-
-1;
-
diff --git a/FS/FS/part_export/domain_sql.pm b/FS/FS/part_export/domain_sql.pm
deleted file mode 100644
index 0ce1b16..0000000
--- a/FS/FS/part_export/domain_sql.pm
+++ /dev/null
@@ -1,238 +0,0 @@
-package FS::part_export::domain_sql;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-#quite a bit of false laziness w/acct_sql - some stuff should be generalized
-#out to a "dababase base class"
-
-tie my %options, 'Tie::IxHash',
- 'datasrc' => { label => 'DBI data source' },
- 'username' => { label => 'Database username' },
- 'password' => { label => 'Database password' },
- 'table' => { label => 'Database table' },
- 'schema' => { label =>
- 'Database schema mapping to Freeside methods.',
- type => 'textarea',
- },
- 'static' => { label =>
- 'Database schema mapping to static values.',
- type => 'textarea',
- },
- 'primary_key' => { label => 'Database primary key' },
-;
-
-tie my %postfix_transport_map, 'Tie::IxHash',
- 'domain' => 'domain'
-;
-my $postfix_transport_map =
- join('\n', map "$_ $postfix_transport_map{$_}",
- keys %postfix_transport_map );
-tie my %postfix_transport_static, 'Tie::IxHash',
- 'transport' => 'virtual:',
-;
-my $postfix_transport_static =
- join('\n', map "$_ $postfix_transport_static{$_}",
- keys %postfix_transport_static );
-
-%info = (
- 'svc' => 'svc_domain',
- 'desc' => 'Real time export of domains to SQL databases '.
- '(postfix, others?)',
- 'options' => \%options,
- 'notes' => <<END
-Export domains (svc_domain records) to SQL databases. Currently this is a
-simple export with a default for Postfix, but it can be extended for other
-uses.
-
-<BR><BR>Use these buttons for useful presets:
-<UL>
- <LI><INPUT TYPE="button" VALUE="postfix_transport" onClick='
- this.form.table.value = "transport";
- this.form.schema.value = "$postfix_transport_map";
- this.form.static.value = "$postfix_transport_static";
- this.form.primary_key.value = "domain";
- '>
-</UL>
-END
-);
-
-sub _schema_map { shift->_map('schema'); }
-sub _static_map { shift->_map('static'); }
-
-sub _map {
- my $self = shift;
- map { /^\s*(\S+)\s*(\S+)\s*$/ } split("\n", $self->option(shift) );
-}
-
-sub _export_insert {
- my($self, $svc_domain) = (shift, shift);
-
- my %schema = $self->_schema_map;
- my %static = $self->_static_map;
-
- my %record = ( ( map { $_ => $static{$_} } keys %static ),
- ( map { my $method = $schema{$_};
- $_ => $svc_domain->$method();
- }
- keys %schema
- )
- );
-
- my $err_or_queue =
- $self->domain_sql_queue(
- $svc_domain->svcnum,
- 'insert',
- $self->option('table'),
- %record
- );
- return $err_or_queue unless ref($err_or_queue);
-
- '';
-}
-
-sub _export_replace {
- my($self, $new, $old) = (shift, shift, shift);
-
- my %schema = $self->_schema_map;
- my %static = $self->_static_map;
-
- my @primary_key = ();
- if ( $self->option('primary_key') =~ /,/ ) {
- foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) {
- my $keymap = $schema{$key};
- push @primary_key, $old->$keymap();
- }
- } else {
- my $keymap = $map{$self->option('primary_key')};
- push @primary_key, $old->$keymap();
- }
-
- my %record = ( ( map { $_ => $static{$_} } keys %static ),
- ( map { my $method = $schema{$_};
- $_ => $new->$method();
- }
- keys %schema
- )
- );
-
- my $err_or_queue = $self->domain_sql_queue(
- $new->svcnum,
- 'replace',
- $self->option('table'),
- $self->option('primary_key'), @primary_key,
- %record,
- );
- return $err_or_queue unless ref($err_or_queue);
- '';
-}
-
-sub _export_delete {
- my ( $self, $svc_domain ) = (shift, shift);
-
- my %schema = $self->_schema_map;
- my %static = $self->_static_map;
-
- my %primary_key = ();
- if ( $self->option('primary_key') =~ /,/ ) {
- foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) {
- my $keymap = $map{$key};
- $primary_key{ $key } = $svc_domain->$keymap();
- }
- } else {
- my $keymap = $map{$self->option('primary_key')};
- $primary_key{ $self->option('primary_key') } = $svc_domain->$keymap(),
- }
-
- my $err_or_queue = $self->domain_sql_queue(
- $svc_domain->svcnum,
- 'delete',
- $self->option('table'),
- %primary_key,
- #$self->option('primary_key') => $svc_domain->$keymap(),
- );
- return $err_or_queue unless ref($err_or_queue);
- '';
-}
-
-sub domain_sql_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::domain_sql::domain_sql_$method",
- };
- $queue->insert(
- $self->option('datasrc'),
- $self->option('username'),
- $self->option('password'),
- @_,
- ) or $queue;
-}
-
-sub domain_sql_insert { #subroutine, not method
- my $dbh = domain_sql_connect(shift, shift, shift);
- my( $table, %record ) = @_;
-
- my $sth = $dbh->prepare(
- "INSERT INTO $table ( ". join(", ", keys %record).
- " ) VALUES ( ". join(", ", map '?', keys %record ). " )"
- ) or die $dbh->errstr;
-
- $sth->execute( values(%record) )
- or die "can't insert into $table table: ". $sth->errstr;
-
- $dbh->disconnect;
-}
-
-sub domain_sql_delete { #subroutine, not method
- my $dbh = domain_sql_connect(shift, shift, shift);
- my( $table, %record ) = @_;
-
- my $sth = $dbh->prepare(
- "DELETE FROM $table WHERE ". join(' AND ', map "$_ = ? ", keys %record )
- ) or die $dbh->errstr;
-
- $sth->execute( map $record{$_}, keys %record )
- or die "can't delete from $table table: ". $sth->errstr;
-
- $dbh->disconnect;
-}
-
-sub domain_sql_replace { #subroutine, not method
- my $dbh = domain_sql_connect(shift, shift, shift);
-
- my( $table, $pkey ) = ( shift, shift );
-
- my %primary_key = ();
- if ( $pkey =~ /,/ ) {
- foreach my $key ( split(/\s*,\s*/, $pkey ) ) {
- $primary_key{$key} = shift;
- }
- } else {
- $primary_key{$pkey} = shift;
- }
-
- my %record = @_;
-
- my $sth = $dbh->prepare(
- "UPDATE $table".
- ' SET '. join(', ', map "$_ = ?", keys %record ).
- ' WHERE '. join(' AND ', map "$_ = ?", keys %primary_key )
- ) or die $dbh->errstr;
-
- $sth->execute( values(%record), values(%primary_key) );
-
- $dbh->disconnect;
-}
-
-sub domain_sql_connect {
- #my($datasrc, $username, $password) = @_;
- #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
- DBI->connect(@_) or die $DBI::errstr;
-}
-
-1;
-
diff --git a/FS/FS/part_export/everyone_net.pm b/FS/FS/part_export/everyone_net.pm
deleted file mode 100644
index e04318e..0000000
--- a/FS/FS/part_export/everyone_net.pm
+++ /dev/null
@@ -1,132 +0,0 @@
-package FS::part_export::everyone_net;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'clientID' => { label=>'clientID' },
- 'password' => { label=>'Password' },
- #'workgroup' => { label=>'Default Workgroup' },
- 'debug' => { label=>'Enable debugging',
- type=>'checkbox' },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to Everyone.net outsourced mail service',
- 'options'=> \%options,
- 'notes' => <<'END'
-Real-time export to
-<a href="http://www.cp.net/">Everyone.net</a> via the XRC Remote API.
-Requires installation of
-<a href="http://search.cpan.org/dist/Net-XRC">Net::XRC</a>
-from CPAN.
-END
-);
-
-sub rebless { shift; }
-
-# experiement: want the status of these right away (don't want account to
-# create or whatever and then get error in the queue from dup username or
-# something), so no queueing
-
-sub _export_insert {
- my( $self, $svc_acct ) = (shift, shift);
-
- eval "use Net::XRC qw(:types);";
- return $@ if $@;
-
- $self->_xrc_command( 'createUser',
- $svc_acct->domain,
- [],
- string($svc_acct->username),
- string($svc_acct->_password),
- );
-}
-
-sub _xrc_command {
- my( $self, $method, $domain, @args ) = @_;
-
- eval "use Net::XRC qw(:types);";
- return $@ if $@;
-
- local($Net::XRC::DEBUG) = 1
- if $self->option('debug');
-
- my $xrc = new Net::XRC (
- 'clientID' => $self->option('clientID'),
- 'password' => $self->option('password'),
- );
-
- my $dresponse = $xrc->lookupMXReadyClientIDByEmailDomain( string($domain) );
- return $dresponse->error unless $dresponse->is_success;
- my $clientID = $dresponse->content;
- return "clientID for domain $domain not found"
- if $clientID == -1;
-
- my $response = $xrc->$method($clientID, @args);
- return $response->error unless $response->is_success;
- '';
-
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
-
- eval "use Net::XRC qw(:types);";
- return $@ if $@;
-
- return "can't change domain with Everyone.net"
- if $old->domain ne $new->domain;
- return "can't change username with Everyone.net"
- if $old->username ne $new->username;
- return '' unless $old->_password ne $new->_password;
-
- $self->_xrc_command( 'setUserPassword',
- $new->domain,
- string($new->username),
- string($new->_password),
- );
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
-
- eval "use Net::XRC qw(:types);";
- return $@ if $@;
-
- $self->_xrc_command( 'deleteUser',
- $svc_acct->domain,
- string($svc_acct->username),
- );
-}
-
-sub _export_suspend {
- my( $self, $svc_acct ) = (shift, shift);
-
- eval "use Net::XRC qw(:types);";
- return $@ if $@;
-
- $self->_xrc_command( 'suspendUser',
- $svc_acct->domain,
- string($svc_acct->username),
- );
-}
-
-sub _export_unsuspend {
- my( $self, $svc_acct ) = (shift, shift);
-
- eval "use Net::XRC qw(:types);";
- return $@ if $@;
-
- $self->_xrc_command( 'unsuspendUser',
- $svc_acct->domain,
- string($svc_acct->username),
- );
-}
-
-1;
-
diff --git a/FS/FS/part_export/forward_shellcommands.pm b/FS/FS/part_export/forward_shellcommands.pm
deleted file mode 100644
index cee24e4..0000000
--- a/FS/FS/part_export/forward_shellcommands.pm
+++ /dev/null
@@ -1,182 +0,0 @@
-package FS::part_export::forward_shellcommands;
-
-use strict;
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote username', default=>'root' },
- 'useradd' => { label=>'Insert command',
- default=>'',
- },
- 'userdel' => { label=>'Delete command',
- default=>'',
- },
- 'usermod' => { label=>'Modify command',
- default=>'',
- },
-;
-
-%info = (
- 'svc' => 'svc_forward',
- 'desc' => 'Run remote commands via SSH, for forwards',
- 'options' => \%options,
- 'notes' => <<'END'
-Run remote commands via SSH, for forwards. You will need to
-<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
-<BR><BR>Use these buttons for some useful presets:
-<UL>
- <LI>
- <INPUT TYPE="button" VALUE="text vpopmail maintenance" onClick='
- this.form.useradd.value = "[ -d /home/vpopmail/domains/$domain/$username ] && { echo \"$destination\" > /home/vpopmail/domains/$domain/$username/.qmail; chown vpopmail:vchkpw /home/vpopmail/domains/$domain/$username/.qmail; }";
- this.form.userdel.value = "rm /home/vpopmail/domains/$domain/$username/.qmail";
- this.form.usermod.value = "mv /home/vpopmail/domains/$old_domain/$old_username/.qmail /home/vpopmail/domains/$new_domain/$new_username; [ \"$old_destination\" != \"$new_destination\" ] && { echo \"$new_destination\" > /home/vpopmail/domains/$new_domain/$new_username/.qmail; chown vpopmail:vchkpw /home/vpopmail/domains/$new_domain/$new_username/.qmail; }";
- '>
- <LI>
- <INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
- this.form.useradd.value = "";
- this.form.userdel.value = "";
- this.form.usermod.value = "";
- '>
-</UL>
-The following variables are available for interpolation (prefixed with
-<code>new_</code> or <code>old_</code> for replace operations):
-<UL>
- <LI><code>$username</code> - username of forward source
- <LI><code>$domain</code> - domain of forward source
- <LI><code>$source</code> - forward source ($username@$domain)
- <LI><code>$destination</code> - forward destination
- <LI>All other fields in <a href="../docs/schema.html#svc_forward">svc_forward</a> are also available.
-</UL>
-END
-);
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self) = shift;
- $self->_export_command('useradd', @_);
-}
-
-sub _export_delete {
- my($self) = shift;
- $self->_export_command('userdel', @_);
-}
-
-sub _export_command {
- my ( $self, $action, $svc_forward ) = (shift, shift, shift);
- my $command = $self->option($action);
- return '' if $command =~ /^\s*$/;
-
- #set variable for the command
- no strict 'vars';
- {
- no strict 'refs';
- ${$_} = $svc_forward->getfield($_) foreach $svc_forward->fields;
- }
-
- if ( $svc_forward->srcsvc ) {
- my $srcsvc_acct = $svc_forward->srcsvc_acct;
- $username = $srcsvc_acct->username;
- $domain = $srcsvc_acct->domain;
- $source = $srcsvc_acct->email;
- } else {
- $source = $svc_forward->src;
- ( $username, $domain ) = split(/\@/, $source);
- }
-
- if ($svc_forward->dstsvc) {
- $destination = $svc_forward->dstsvc_acct->email;
- } else {
- $destination = $svc_forward->dst;
- }
-
- #done setting variables for the command
-
- $self->shellcommands_queue( $svc_forward->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => eval(qq("$command")),
- );
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
- my $command = $self->option('usermod');
-
- #set variable for the command
- no strict 'vars';
- {
- no strict 'refs';
- ${"old_$_"} = $old->getfield($_) foreach $old->fields;
- ${"new_$_"} = $new->getfield($_) foreach $new->fields;
- }
-
- if ( $old->srcsvc ) {
- my $srcsvc_acct = $old->srcsvc_acct;
- $old_username = $srcsvc_acct->username;
- $old_domain = $srcsvc_acct->domain;
- $old_source = $srcsvc_acct->email;
- } else {
- $old_source = $old->src;
- ( $old_username, $old_domain ) = split(/\@/, $old_source);
- }
-
- if ( $old->dstsvc ) {
- $old_destination = $old->dstsvc_acct->email;
- } else {
- $old_destination = $old->dst;
- }
-
- if ( $new->srcsvc ) {
- my $srcsvc_acct = $new->srcsvc_acct;
- $new_username = $srcsvc_acct->username;
- $new_domain = $srcsvc_acct->domain;
- $new_source = $srcsvc_acct->email;
- } else {
- $new_source = $new->src;
- ( $new_username, $new_domain ) = split(/\@/, $new_source);
- }
-
- if ( $new->dstsvc ) {
- $new_destination = $new->dstsvc_acct->email;
- } else {
- $new_destination = $new->dst;
- }
-
- #done setting variables for the command
-
- $self->shellcommands_queue( $new->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => eval(qq("$command")),
- );
-}
-
-#a good idea to queue anything that could fail or take any time
-sub shellcommands_queue {
- my( $self, $svcnum ) = (shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::forward_shellcommands::ssh_cmd",
- };
- $queue->insert( @_ );
-}
-
-sub ssh_cmd { #subroutine, not method
- use Net::SSH '0.08';
- &Net::SSH::ssh_cmd( { @_ } );
-}
-
-#sub shellcommands_insert { #subroutine, not method
-#}
-#sub shellcommands_replace { #subroutine, not method
-#}
-#sub shellcommands_delete { #subroutine, not method
-#}
-
-1;
-
diff --git a/FS/FS/part_export/http.pm b/FS/FS/part_export/http.pm
deleted file mode 100644
index 55d8329..0000000
--- a/FS/FS/part_export/http.pm
+++ /dev/null
@@ -1,134 +0,0 @@
-package FS::part_export::http;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'method' => { label =>'Method',
- type =>'select',
- #options =>[qw(POST GET)],
- options =>[qw(POST)],
- default =>'POST' },
- 'url' => { label => 'URL', default => 'http://', },
- 'insert_data' => {
- label => 'Insert data',
- type => 'textarea',
- default => join("\n",
- 'DomainName $svc_x->domain',
- 'Email ( grep { $_ !~ /^(POST|FAX)$/ } $svc_x->cust_svc->cust_pkg->cust_main->invoicing_list)[0]',
- 'test 1',
- 'reseller $svc_x->cust_svc->cust_pkg->part_pkg->pkg =~ /reseller/i',
- ),
- },
- 'delete_data' => {
- label => 'Delete data',
- type => 'textarea',
- default => join("\n",
- ),
- },
- 'replace_data' => {
- label => 'Replace data',
- type => 'textarea',
- default => join("\n",
- ),
- },
-;
-
-%info = (
- 'svc' => 'svc_domain',
- 'desc' => 'Send an HTTP or HTTPS GET or POST request',
- 'options' => \%options,
- 'notes' => <<'END'
-Send an HTTP or HTTPS GET or POST to the specified URL. For HTTPS support,
-<a href="http://search.cpan.org/dist/Crypt-SSLeay">Crypt::SSLeay</a>
-or <a href="http://search.cpan.org/dist/IO-Socket-SSL">IO::Socket::SSL</a>
-is required.
-END
-);
-
-sub rebless { shift; }
-
-sub _export_insert {
- my $self = shift;
- $self->_export_command('insert', @_);
-}
-
-sub _export_delete {
- my $self = shift;
- $self->_export_command('delete', @_);
-}
-
-sub _export_command {
- my( $self, $action, $svc_x ) = ( shift, shift, shift );
-
- return unless $self->option("${action}_data");
-
- $self->http_queue( $svc_x->svcnum,
- $self->option('method'),
- $self->option('url'),
- map {
- /^\s*(\S+)\s+(.*)$/ or /()()/;
- my( $field, $value_expression ) = ( $1, $2 );
- my $value = eval $value_expression;
- die $@ if $@;
- ( $field, $value );
- } split(/\n/, $self->option("${action}_data") )
- );
-
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = ( shift, shift, shift );
-
- return unless $self->option('replace_data');
-
- $self->http_queue( $svc_x->svcnum,
- $self->option('method'),
- $self->option('url'),
- map {
- /^\s*(\S+)\s+(.*)$/ or /()()/;
- my( $field, $value_expression ) = ( $1, $2 );
- die $@ if $@;
- ( $field, $value );
- } split(/\n/, $self->option('replace_data') )
- );
-
-}
-
-sub http_queue {
- my($self, $svcnum) = (shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::http::http",
- };
- $queue->insert( @_ );
-}
-
-sub http {
- my($method, $url, @data) = @_;
-
- $method = lc($method);
-
- eval "use LWP::UserAgent;";
- die "using LWP::UserAgent: $@" if $@;
- eval "use HTTP::Request::Common;";
- die "using HTTP::Request::Common: $@" if $@;
-
- my $ua = LWP::UserAgent->new;
-
- #my $response = $ua->$method(
- # $url, \%data,
- # 'Content-Type'=>'application/x-www-form-urlencoded'
- #);
- my $req = HTTP::Request::Common::POST( $url, \@data );
- my $response = $ua->request($req);
-
- die $response->error_as_HTML if $response->is_error;
-
-}
-
-1;
-
diff --git a/FS/FS/part_export/infostreet.pm b/FS/FS/part_export/infostreet.pm
deleted file mode 100644
index ef16c7c..0000000
--- a/FS/FS/part_export/infostreet.pm
+++ /dev/null
@@ -1,277 +0,0 @@
-package FS::part_export::infostreet;
-
-use vars qw(@ISA %info %infostreet2cust_main $DEBUG);
-use Tie::IxHash;
-use FS::UID qw(dbh);
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'url' => { label=>'XML-RPC Access URL', },
- 'login' => { label=>'InfoStreet login', },
- 'password' => { label=>'InfoStreet password', },
- 'groupID' => { label=>'InfoStreet groupID', },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to InfoStreet streetSmartAPI',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => <<'END'
-Real-time export to
-<a href="http://www.infostreet.com/">InfoStreet</a> streetSmartAPI.
-Requires installation of
-<a href="http://search.cpan.org/dist/Frontier-Client">Frontier::Client</a> from CPAN.
-END
-);
-
-$DEBUG = 0;
-
-%infostreet2cust_main = (
- 'firstName' => 'first',
- 'lastName' => 'last',
- 'address1' => 'address1',
- 'address2' => 'address2',
- 'city' => 'city',
- 'state' => 'state',
- 'zipCode' => 'zip',
- 'country' => 'country',
- 'phoneNumber' => 'daytime',
- 'faxNumber' => 'night', #noment-request...
-);
-
-sub rebless { shift; }
-
-sub _export_insert {
- my( $self, $svc_acct ) = (shift, shift);
- my $cust_main = $svc_acct->cust_svc->cust_pkg->cust_main;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $err_or_queue = $self->infostreet_err_or_queue( $svc_acct->svcnum,
- 'createUser', $svc_acct->username, $svc_acct->_password );
- return $err_or_queue unless ref($err_or_queue);
- my $jobnum = $err_or_queue->jobnum;
-
- my %contact_info = ( map {
- $_ => $cust_main->getfield( $infostreet2cust_main{$_} );
- } keys %infostreet2cust_main );
-
- my @emails = grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list;
- $contact_info{'email'} = $emails[0] if @emails;
-
- #this one is kinda noment-specific
- $contact_info{'organization'} = $cust_main->agent->agent;
-
- $err_or_queue = $self->infostreet_queueContact( $svc_acct->svcnum,
- $svc_acct->username, %contact_info );
- return $err_or_queue unless ref($err_or_queue);
-
- # If a quota has been specified set the quota because it is not the default
- $err_or_queue = $self->infostreet_queueSetQuota( $svc_acct->svcnum,
- $svc_acct->username, $svc_acct->quota ) if $svc_acct->quota;
- return $err_or_queue unless ref($err_or_queue);
-
- my $error = $err_or_queue->depend_insert( $jobnum );
- return $error if $error;
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
- return "can't change username with InfoStreet"
- if $old->username ne $new->username;
-
- # If the quota has changed then do the export to setQuota
- my $err_or_queue = $self->infostreet_queueSetQuota( $new->svcnum, $new->username, $new->quota )
- if ( $old->quota != $new->quota );
- return $err_or_queue unless ref($err_or_queue);
-
-
- return '' unless $old->_password ne $new->_password;
- $self->infostreet_queue( $new->svcnum,
- 'passwd', $new->username, $new->_password );
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- $self->infostreet_queue( $svc_acct->svcnum,
- 'purgeAccount,releaseUsername', $svc_acct->username );
-}
-
-sub _export_suspend {
- my( $self, $svc_acct ) = (shift, shift);
- $self->infostreet_queue( $svc_acct->svcnum,
- 'setStatus', $svc_acct->username, 'DISABLED' );
-}
-
-sub _export_unsuspend {
- my( $self, $svc_acct ) = (shift, shift);
- $self->infostreet_queue( $svc_acct->svcnum,
- 'setStatus', $svc_acct->username, 'ACTIVE' );
-}
-
-sub infostreet_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => 'FS::part_export::infostreet::infostreet_command',
- };
- $queue->insert(
- $self->option('url'),
- $self->option('login'),
- $self->option('password'),
- $self->option('groupID'),
- $method,
- @_,
- );
-}
-
-#ick false laziness
-sub infostreet_err_or_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => 'FS::part_export::infostreet::infostreet_command',
- };
- $queue->insert(
- $self->option('url'),
- $self->option('login'),
- $self->option('password'),
- $self->option('groupID'),
- $method,
- @_,
- ) or $queue;
-}
-
-sub infostreet_queueContact {
- my( $self, $svcnum ) = (shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => 'FS::part_export::infostreet::infostreet_setContact',
- };
- $queue->insert(
- $self->option('url'),
- $self->option('login'),
- $self->option('password'),
- $self->option('groupID'),
- @_,
- ) or $queue;
-}
-
-sub infostreet_setContact {
- my($url, $is_username, $is_password, $groupID, $username, %contact_info) = @_;
- my $accountID = infostreet_command($url, $is_username, $is_password, $groupID,
- 'getAccountID', $username);
- foreach my $field ( keys %contact_info ) {
- infostreet_command($url, $is_username, $is_password, $groupID,
- 'setContactField', [ 'int'=>$accountID ], $field, $contact_info{$field} );
- }
-
-}
-
-sub infostreet_queueSetQuota {
-
- my( $self, $svcnum) = (shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => 'FS::part_export::infostreet::infostreet_setQuota',
- };
-
- $queue->insert(
- $self->option('url'),
- $self->option('login'),
- $self->option('password'),
- $self->option('groupID'),
- @_,
- ) or $queue;
-
-}
-
-sub infostreet_setQuota {
- my($url, $is_username, $is_password, $groupID, $username, $quota) = @_;
- infostreet_command($url, $is_username, $is_password, $groupID, 'setQuota', $username, [ 'int'=> $quota ] );
-}
-
-
-sub infostreet_command { #subroutine, not method
- my($url, $username, $password, $groupID, $method, @args) = @_;
-
- warn "[FS::part_export::infostreet] $method ".join(' ', @args)."\n" if $DEBUG;
-
- #quelle hack
- if ( $method =~ /,/ ) {
- foreach my $part ( split(/,\s*/, $method) ) {
- infostreet_command($url, $username, $password, $groupID, $part, @args);
- }
- return;
- }
-
- eval "use Frontier::Client;";
- die $@ if $@;
-
- eval 'sub Frontier::RPC2::String::repr {
- my $self = shift;
- my $value = $$self;
- $value =~ s/([&<>\"])/$Frontier::RPC2::char_entities{$1}/ge;
- $value;
- }';
- die $@ if $@;
-
- my $conn = Frontier::Client->new( url => $url );
- my $key_result = $conn->call( 'authenticate', $username, $password, $groupID);
- my %key_result = _infostreet_parse($key_result);
- die $key_result{error} unless $key_result{success};
- my $key = $key_result{data};
-
- #my $result = $conn->call($method, $key, @args);
- my $result = $conn->call( $method, $key,
- map {
- if ( ref($_) ) {
- my( $type, $value) = @{$_};
- $conn->$type($value);
- } else {
- $conn->string($_);
- }
- } @args );
- my %result = _infostreet_parse($result);
- die $result{error} unless $result{success};
-
- $result->{data};
-
-}
-
-#sub infostreet_command_byid { #subroutine, not method;
-# my($url, $username, $password, $groupID, $method, @args ) = @_;
-#
-# infostreet_command
-#
-#}
-
-sub _infostreet_parse { #subroutine, not method
- my $arg = shift;
- map {
- my $value = $arg->{$_};
- #warn ref($value);
- $value = $value->value()
- if ref($value) && $value->isa('Frontier::RPC2::DataType');
- $_=>$value;
- } keys %$arg;
-}
-
-1;
-
diff --git a/FS/FS/part_export/ldap.pm b/FS/FS/part_export/ldap.pm
deleted file mode 100644
index 823d99d..0000000
--- a/FS/FS/part_export/ldap.pm
+++ /dev/null
@@ -1,294 +0,0 @@
-package FS::part_export::ldap;
-
-use vars qw(@ISA %info @saltset);
-use Tie::IxHash;
-use FS::Record qw( dbh );
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'dn' => { label=>'Root DN' },
- 'password' => { label=>'Root DN password' },
- 'userdn' => { label=>'User DN' },
- 'attributes' => { label=>'Attributes',
- type=>'textarea',
- default=>join("\n",
- 'uid $username',
- 'mail $username\@$domain',
- 'uidno $uid',
- 'gidno $gid',
- 'cn $first',
- 'sn $last',
- 'mailquota $quota',
- 'vmail',
- 'location',
- 'mailtag',
- 'mailhost',
- 'mailmessagestore $dir',
- 'userpassword $crypt_password',
- 'hint',
- 'answer $sec_phrase',
- 'objectclass top,person,inetOrgPerson',
- ),
- },
- 'radius' => { label=>'Export RADIUS attributes', type=>'checkbox', },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to LDAP',
- 'options' => \%options,
- 'notes' => <<'END'
-Real-time export to arbitrary LDAP attributes. Requires installation of
-<a href="http://search.cpan.org/dist/Net-LDAP">Net::LDAP</a> from CPAN.
-END
-);
-
-@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self, $svc_acct) = (shift, shift);
-
- #false laziness w/shellcommands.pm
- {
- no strict 'refs';
- ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields;
- ${$_} = $svc_acct->$_() foreach qw( domain );
- my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
- if ( $cust_pkg ) {
- my $cust_main = $cust_pkg->cust_main;
- ${$_} = $cust_main->getfield($_) foreach qw(first last);
- }
- }
- $crypt_password = ''; #surpress "used only once" warnings
- $crypt_password = '{crypt}'. crypt( $svc_acct->_password,
- $saltset[int(rand(64))].$saltset[int(rand(64))] );
-
- my $username_attrib;
- my %attrib = map { /^\s*(\w+)\s+(.*\S)\s*$/;
- $username_attrib = $1 if $2 eq '$username';
- ( $1 => eval(qq("$2")) ); }
- grep { /^\s*(\w+)\s+(.*\S)\s*$/ }
- split("\n", $self->option('attributes'));
-
- if ( $self->option('radius') ) {
- foreach my $table (qw(reply check)) {
- my $method = "radius_$table";
- my %radius = $svc_acct->$method();
- foreach my $radius ( keys %radius ) {
- ( my $ldap = $radius ) =~ s/\-//g;
- $attrib{$ldap} = $radius{$radius};
- }
- }
- }
-
- my $err_or_queue = $self->ldap_queue( $svc_acct->svcnum, 'insert',
- #$svc_acct->username,
- $username_attrib,
- %attrib );
- return $err_or_queue unless ref($err_or_queue);
-
- #groups with LDAP?
- #my @groups = $svc_acct->radius_groups;
- #if ( @groups ) {
- # my $err_or_queue = $self->ldap_queue(
- # $svc_acct->svcnum, 'usergroup_insert',
- # $svc_acct->username, @groups );
- # return $err_or_queue unless ref($err_or_queue);
- #}
-
- '';
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- return "can't (yet?) change username with ldap"
- if $old->username ne $new->username;
-
- return "ldap replace unimplemented";
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $jobnum = '';
- #if ( $old->username ne $new->username ) {
- # my $err_or_queue = $self->ldap_queue( $new->svcnum, 'rename',
- # $new->username, $old->username );
- # unless ( ref($err_or_queue) ) {
- # $dbh->rollback if $oldAutoCommit;
- # return $err_or_queue;
- # }
- # $jobnum = $err_or_queue->jobnum;
- #}
-
- foreach my $table (qw(reply check)) {
- my $method = "radius_$table";
- my %new = $new->$method();
- my %old = $old->$method();
- if ( grep { !exists $old{$_} #new attributes
- || $new{$_} ne $old{$_} #changed
- } keys %new
- ) {
- my $err_or_queue = $self->ldap_queue( $new->svcnum, 'insert',
- $table, $new->username, %new );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- if ( $jobnum ) {
- my $error = $err_or_queue->depend_insert( $jobnum );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- my @del = grep { !exists $new{$_} } keys %old;
- if ( @del ) {
- my $err_or_queue = $self->ldap_queue( $new->svcnum, 'attrib_delete',
- $table, $new->username, @del );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- if ( $jobnum ) {
- my $error = $err_or_queue->depend_insert( $jobnum );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
- }
-
- # (sorta) false laziness with FS::svc_acct::replace
- my @oldgroups = @{$old->usergroup}; #uuuh
- my @newgroups = $new->radius_groups;
- my @delgroups = ();
- foreach my $oldgroup ( @oldgroups ) {
- if ( grep { $oldgroup eq $_ } @newgroups ) {
- @newgroups = grep { $oldgroup ne $_ } @newgroups;
- next;
- }
- push @delgroups, $oldgroup;
- }
-
- if ( @delgroups ) {
- my $err_or_queue = $self->ldap_queue( $new->svcnum, 'usergroup_delete',
- $new->username, @delgroups );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- if ( $jobnum ) {
- my $error = $err_or_queue->depend_insert( $jobnum );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- if ( @newgroups ) {
- my $err_or_queue = $self->ldap_queue( $new->svcnum, 'usergroup_insert',
- $new->username, @newgroups );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- if ( $jobnum ) {
- my $error = $err_or_queue->depend_insert( $jobnum );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- return "ldap delete unimplemented";
- my $err_or_queue = $self->ldap_queue( $svc_acct->svcnum, 'delete',
- $svc_acct->username );
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-sub ldap_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::ldap::ldap_$method",
- };
- $queue->insert(
- $self->machine,
- $self->option('dn'),
- $self->option('password'),
- $self->option('userdn'),
- @_,
- ) or $queue;
-}
-
-sub ldap_insert { #subroutine, not method
- my $ldap = ldap_connect(shift, shift, shift);
- my( $userdn, $username_attrib, %attrib ) = @_;
-
- $userdn = "$username_attrib=$attrib{$username_attrib}, $userdn"
- if $username_attrib;
- #icky hack, but should be unsurprising to the LDAPers
- foreach my $key ( grep { $attrib{$_} =~ /,/ } keys %attrib ) {
- $attrib{$key} = [ split(/,/, $attrib{$key}) ];
- }
-
- my $status = $ldap->add( $userdn, attrs => [ %attrib ] );
- die 'LDAP error: '. $status->error. "\n" if $status->is_error;
-
- $ldap->unbind;
-}
-
-#sub ldap_delete { #subroutine, not method
-# my $dbh = ldap_connect(shift, shift, shift);
-# my $username = shift;
-#
-# foreach my $table (qw( radcheck radreply usergroup )) {
-# my $sth = $dbh->prepare( "DELETE FROM $table WHERE UserName = ?" );
-# $sth->execute($username)
-# or die "can't delete from $table table: ". $sth->errstr;
-# }
-# $dbh->disconnect;
-#}
-
-sub ldap_connect {
- my( $machine, $dn, $password ) = @_;
- my %bind_options;
- $bind_options{password} = $password if length($password);
-
- eval "use Net::LDAP";
- die $@ if $@;
-
- my $ldap = Net::LDAP->new($machine) or die $@;
- my $status = $ldap->bind( $dn, %bind_options );
- die 'LDAP error: '. $status->error. "\n" if $status->is_error;
-
- $ldap;
-}
-
-1;
-
diff --git a/FS/FS/part_export/nas_wrapper.pm b/FS/FS/part_export/nas_wrapper.pm
deleted file mode 100644
index 2499ba3..0000000
--- a/FS/FS/part_export/nas_wrapper.pm
+++ /dev/null
@@ -1,311 +0,0 @@
-package FS::part_export::nas_wrapper;
-
-=head1 FS::part_export::nas_wrapper
-
-This is a meta-export that triggers other exports for FS::svc_broadband objects
-based on a set of configurable conditions. These conditions are defined by the
-following FS::router virtual fields:
-
-=over 4
-
-=item nas_conf - Per-router meta-export configuration. See L</"nas_conf Syntax">.
-
-=back
-
-=head2 nas_conf Syntax
-
-export_name|routernum[,routernum]|[field,condition[,field,condition]][||...]
-
-=over 4
-
-=item export_name - Name or exportnum of the export to be executed. In order to specify export options you must use the exportnum form. (ex. 'router' for FS::part_export::router).
-
-=item routernum - FS::router routernum corresponding to the desired FS::router for which this export will be run.
-
-=item field - FS::svc_broadband field (real or virtual). The following condition (regex) will be matched against the value of this field.
-
-=item condition - A regular expression to be match against the value of the previously listed FS::svc_broadband field.
-
-=back
-
-If multiple routernum's are specified, then the export will be triggered for each router listed. If multiple field/condition pairs are present, then the results of the matches will be and'd. Note that if a false match is found, the rest of the matches may not be checked.
-
-You can specify multiple export/router/condition sets by concatenating them with '||'.
-
-=cut
-
-use strict;
-use vars qw(@ISA %info $me $DEBUG);
-
-use FS::Record qw(qsearchs);
-use FS::part_export;
-
-use Tie::IxHash;
-use Data::Dumper qw(Dumper);
-
-@ISA = qw(FS::part_export);
-$me = '[' . __PACKAGE__ . ']';
-$DEBUG = 0;
-
-%info = (
- 'svc' => 'svc_broadband',
- 'desc' => 'A meta-export that triggers other svc_broadband exports.',
- 'options' => {},
- 'notes' => '',
-);
-
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self) = shift;
- $self->_export_command('insert', @_);
-}
-
-sub _export_delete {
- my($self) = shift;
- $self->_export_command('delete', @_);
-}
-
-sub _export_suspend {
- my($self) = shift;
- $self->_export_command('suspend', @_);
-}
-
-sub _export_unsuspend {
- my($self) = shift;
- $self->_export_command('unsuspend', @_);
-}
-
-sub _export_replace {
- my($self) = shift;
- $self->_export_command('replace', @_);
-}
-
-sub _export_command {
- my ( $self, $action, $svc_broadband) = (shift, shift, shift);
-
- my ($new, $old);
- if ($action eq 'replace') {
- $new = $svc_broadband;
- $old = shift;
- }
-
- my $router = $svc_broadband->addr_block->router;
-
- return '' unless grep(/^nas_conf$/, $router->fields);
- my $nas_conf = $router->nas_conf;
-
- my $child_exports = &_parse_nas_conf($nas_conf);
-
- my $error = '';
-
- my $queue_child_exports = {};
-
- # Similar to FS::svc_Common::replace, calling insert, delete, and replace
- # exports where necessary depending on which conditions match.
- if ($action eq 'replace') {
-
- my @new_child_exports = ();
- my @old_child_exports = ();
-
- # Find all the matching "new" child exports.
- foreach my $child_export (@$child_exports) {
- my $match = &_test_child_export_conditions(
- $child_export->{'conditions'},
- $new,
- );
-
- if ($match) {
- push @new_child_exports, $child_export;
- }
- }
-
- # Find all the matching "old" child exports.
- foreach my $child_export (@$child_exports) {
- my $match = &_test_child_export_conditions(
- $child_export->{'conditions'},
- $old,
- );
-
- if ($match) {
- push @old_child_exports, $child_export;
- }
- }
-
- # Insert exports for new.
- push @{$queue_child_exports->{'insert'}}, (
- map {
- my $new_child_export = $_;
- if (! grep { $new_child_export eq $_ } @old_child_exports) {
- $new_child_export->{'args'} = [ $new ];
- $new_child_export;
- } else {
- ();
- }
- } @new_child_exports
- );
-
- # Replace exports for new and old.
- push @{$queue_child_exports->{'replace'}}, (
- map {
- my $new_child_export = $_;
- if (grep { $new_child_export eq $_ } @old_child_exports) {
- $new_child_export->{'args'} = [ $new, $old ];
- $new_child_export;
- } else {
- ();
- }
- } @new_child_exports
- );
-
- # Delete exports for old.
- push @{$queue_child_exports->{'delete'}}, (
- grep {
- my $old_child_export = $_;
- if (! grep { $old_child_export eq $_ } @new_child_exports) {
- $old_child_export->{'args'} = [ $old ];
- $old_child_export;
- } else {
- ();
- }
- } @old_child_exports
- );
-
- } else {
-
- foreach my $child_export (@$child_exports) {
- my $match = &_test_child_export_conditions(
- $child_export->{'conditions'},
- $svc_broadband,
- );
-
- if ($match) {
- $child_export->{'args'} = [ $svc_broadband ];
- push @{$queue_child_exports->{$action}}, $child_export;
- }
- }
-
- }
-
- warn "[debug]$me Dispatching child exports... "
- . &Dumper($queue_child_exports) if $DEBUG;
-
- # Actually call the child exports now, with their preset action and arguments.
- foreach my $_action (keys(%$queue_child_exports)) {
-
- foreach my $_child_export (@{$queue_child_exports->{$_action}}) {
- $error = &_dispatch_child_export(
- $_child_export,
- $_action,
- @{$_child_export->{'args'}},
- @_,
- );
-
- # Bail if there's an error queueing one of the exports.
- # This will all get rolled-back.
- return $error if $error;
- }
-
- }
-
- return '';
-
-}
-
-
-sub _parse_nas_conf {
-
- my $nas_conf = shift;
- my @child_exports = ();
-
- foreach my $cond_set ($nas_conf =~ m/(.*?[^\\])(?:\|\||$)/g) {
-
- warn "[debug]$me cond_set is '$cond_set'" if $DEBUG;
-
- my @args = $cond_set =~ m/(.*?[^\\])(?:\||$)/g;
-
- my %child_export = (
- 'export' => $args[0],
- 'routernum' => [ split(/,\s*/, $args[1]) ],
- 'conditions' => { @args[2..$#args] },
- );
-
- warn "[debug]$me " . Dumper(\%child_export) if $DEBUG;
-
- push @child_exports, { %child_export };
-
- }
-
- return \@child_exports;
-
-}
-
-sub _dispatch_child_export {
-
- my ($child_export, $action, @args) = (shift, shift, @_);
-
- my $child_export_name = $child_export->{'export'};
- my @routernums = @{$child_export->{'routernum'}};
-
- my $error = '';
-
- # And the real hack begins...
-
- my $child_part_export;
- if ($child_export_name =~ /^(\d+)$/) {
- my $exportnum = $1;
- $child_part_export = qsearchs('part_export', { exportnum => $exportnum });
- unless ($child_part_export) {
- return "No such FS::part_export with exportnum '$exportnum'";
- }
-
- $child_export_name = $child_part_export->exporttype;
- } else {
- $child_part_export = new FS::part_export {
- 'exporttype' => $child_export_name,
- 'machine' => 'bogus',
- };
- }
-
- warn "[debug]$me running export '$child_export_name' for routernum(s) '"
- . join(',', @routernums) . "'" if $DEBUG;
-
- my $cmd_method = "_export_$action";
-
- foreach my $routernum (@routernums) {
- $error ||= $child_part_export->$cmd_method(
- @args,
- 'routernum' => $routernum,
- );
- last if $error;
- }
-
- warn "[debug]$me export '$child_export_name' returned '$error'"
- if $DEBUG;
-
- return $error;
-
-}
-
-sub _test_child_export_conditions {
-
- my ($conditions, $svc_broadband) = (shift, shift);
-
- my $match = 1;
- foreach my $cond_field (keys %$conditions) {
- my $cond_regex = $conditions->{$cond_field};
- warn "[debug]$me Condition: $cond_field =~ /$cond_regex/" if $DEBUG;
- unless ($svc_broadband->get($cond_field) =~ /$cond_regex/) {
- $match = 0;
- last;
- }
- }
-
- return $match;
-
-}
-
-
-1;
-
diff --git a/FS/FS/part_export/null.pm b/FS/FS/part_export/null.pm
deleted file mode 100644
index 0145af3..0000000
--- a/FS/FS/part_export/null.pm
+++ /dev/null
@@ -1,13 +0,0 @@
-package FS::part_export::null;
-
-use vars qw(@ISA);
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-sub rebless { shift; }
-
-sub _export_insert {}
-sub _export_replace {}
-sub _export_delete {}
-
diff --git a/FS/FS/part_export/passwdfile.pm b/FS/FS/part_export/passwdfile.pm
deleted file mode 100644
index 2978d25..0000000
--- a/FS/FS/part_export/passwdfile.pm
+++ /dev/null
@@ -1,18 +0,0 @@
-package FS::part_export::passwdfile;
-
-use strict;
-use vars qw(@ISA %options);
-use Tie::IxHash;
-use FS::part_export::null;
-
-@ISA = qw(FS::part_export::null);
-
-tie %options, 'Tie::IxHash',
- 'crypt' => { label=>'Password encryption',
- type=>'select', options=>[qw(crypt md5)],
- default=>'crypt',
- },
-;
-
-1;
-
diff --git a/FS/FS/part_export/postfix.pm b/FS/FS/part_export/postfix.pm
deleted file mode 100644
index 4fd19ee..0000000
--- a/FS/FS/part_export/postfix.pm
+++ /dev/null
@@ -1,32 +0,0 @@
-package FS::part_export::postfix;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export::null;
-
-@ISA = qw(FS::part_export::null);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote username', default=>'root' },
- 'aliases' => { label=>'aliases file location', default=>'/etc/aliases' },
- 'virtual' => { label=>'virtual file location', default=>'/etc/postfix/virtual' },
- 'mydomain' => { label=>'local domain', default=>'' },
- 'newaliases' => { label=>'newaliases command', default=>'newaliases' },
- 'postmap' => { label=>'postmap command',
- default=>'postmap hash:/etc/postfix/virtual', },
- 'reload' => { label=>'reload command',
- default=>'postfix reload' },
-;
-
-%info = (
- 'svc' => 'svc_forward',
- 'desc' => 'Postfix text files',
- 'options' => \%options,
- 'notes' => <<'END'
-Batch export of Postfix aliases and virtual files.
-<a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
-must be installed. Run bin/postfix.export to export the files.
-END
-);
-
-1;
diff --git a/FS/FS/part_export/prizm.pm b/FS/FS/part_export/prizm.pm
deleted file mode 100644
index 75b10a7..0000000
--- a/FS/FS/part_export/prizm.pm
+++ /dev/null
@@ -1,540 +0,0 @@
-package FS::part_export::prizm;
-
-use vars qw(@ISA %info %options $DEBUG);
-use Tie::IxHash;
-use FS::Record qw(fields dbh);
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-$DEBUG = 1;
-
-tie %options, 'Tie::IxHash',
- 'url' => { label => 'Northbound url', default=>'https://localhost:8443/prizm/nbi' },
- 'user' => { label => 'Northbound username', default=>'nbi' },
- 'password' => { label => 'Password', default => '' },
- 'ems' => { label => 'Full EMS', type => 'checkbox' },
- 'always_bam' => { label => 'Always activate/suspend authentication', type => 'checkbox' },
- 'element_name_length' => { label => 'Size of siteName (best left blank)' },
-;
-
-my $notes = <<'EOT';
-Real-time export of <b>svc_broadband</b>, <b>cust_pkg</b>, and <b>cust_main</b>
-record data to Motorola
-<a href="http://motorola.canopywireless.com/products/prizm/">Canopy Prizm
-software</a> via the Northbound interface.<br><br>
-
-Freeside will attempt to create an element in an existing network with the
-values provided in svc_broadband. Of particular interest are
-<ul>
- <li> mac address - used to identify the element
- <li> vlan profile - an exact match for a vlan profiles defined in prizm
- <li> ip address - defines the management ip address of the prizm element
- <li> latitude - GPS latitude
- <li> longitude - GPS longitude
- <li> altitude - GPS altitude
-</ul>
-
-In addition freeside attempts to set the service plan name in prizm to the
-name of the package in which the service resides.
-
-The service is associated with a customer in prizm as well, and freeside
-will create the customer should none already exist with import id matching
-the freeside customer number. The following fields are set.
-
-<ul>
- <li> importId - the freeside customer number
- <li> customerType - freeside
- <li> customerName - the name associated with the freeside shipping address
- <li> address1 - the shipping address
- <li> address2
- <li> city
- <li> state
- <li> zipCode
- <li> country
- <li> workPhone - the daytime phone number
- <li> homePhone - the night phone number
- <li> freesideId - the freeside customer number
-</ul>
-
- Additionally set on the element are
-<ul>
- <li> Site Name - The shipping name followed by the service broadband description field
- <li> Site Location - the shipping address
- <li> Site Contact - the daytime and night phone numbers
-</ul>
-
-Freeside provisions, suspends, and unsuspends elements BAM only unless the
-'Full EMS' checkbox is checked.<br><br>
-
-When freeside provisions an element the siteName is copied internally by
-prizm in such a manner that it is possible for the value to exceed the size
-of the column used in the prizm database. Therefore freeside truncates
-by default this value to 50 characters. It is thought that this
-column is the account_name column of the element_user_account table. It
-may be possible to lift this limit by modifying the prizm database and
-setting a new appropriate value on this export. This is untested and
-possibly harmful.
-
-EOT
-
-%info = (
- 'svc' => 'svc_broadband',
- 'desc' => 'Real-time export to Northbound Interface',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => $notes,
-);
-
-sub prizm_command {
- my ($self,$namespace,$method) = (shift,shift,shift);
-
- eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
- die $@ if $@;
-
- my $prizm = new Net::Prizm (
- namespace => $namespace,
- url => $self->option('url'),
- user => $self->option('user'),
- password => $self->option('password'),
- );
-
- $prizm->$method(@_);
-}
-
-sub queued_prizm_command { # subroutine
- my( $url, $user, $password, $namespace, $method, @args ) = @_;
-
- eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
- die $@ if $@;
-
- my $prizm = new Net::Prizm (
- namespace => $namespace,
- url => $url,
- user => $user,
- password => $password,
- );
-
- $err_or_som = $prizm->$method( @args);
-
- die $err_or_som
- unless ref($err_or_som);
-
- '';
-
-}
-
-sub _export_insert {
- my( $self, $svc ) = ( shift, shift );
-
- my $cust_main = $svc->cust_svc->cust_pkg->cust_main;
-
- my $err_or_som = $self->prizm_command('CustomerIfService', 'getCustomers',
- ['import_id'],
- [$cust_main->custnum],
- ['='],
- );
- return $err_or_som
- unless ref($err_or_som);
-
- my $pre = '';
- if ( defined $cust_main->dbdef_table->column('ship_last') ) {
- $pre = $cust_main->ship_last ? 'ship_' : '';
- }
- my $name = $pre ? $cust_main->ship_name : $cust_main->name;
- my $location = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
- qw (address1 address2 city state zip)
- );
- my $contact = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
- qw (daytime night)
- );
-
- my $pcustomer;
- if ($err_or_som->result->[0]) {
- $pcustomer = $err_or_som->result->[0]->customerId;
- }else{
- my $chashref = $cust_main->hashref;
- my $customerinfo = {
- importId => $cust_main->custnum,
- customerName => $name,
- customerType => 'freeside',
- address1 => $chashref->{"${pre}address1"},
- address2 => $chashref->{"${pre}address2"},
- city => $chashref->{"${pre}city"},
- state => $chashref->{"${pre}state"},
- zipCode => $chashref->{"${pre}zip"},
- workPhone => $chashref->{"${pre}daytime"},
- homePhone => $chashref->{"${pre}night"},
- email => @{[$cust_main->invoicing_list_emailonly]}[0],
- extraFieldNames => [ 'country', 'freesideId',
- ],
- extraFieldValues => [ $chashref->{"${pre}country"}, $cust_main->custnum,
- ],
- };
-
- $err_or_som = $self->prizm_command('CustomerIfService', 'addCustomer',
- $customerinfo);
- return $err_or_som
- unless ref($err_or_som);
-
- $pcustomer = $err_or_som->result;
- }
- warn "multiple prizm customers found for $cust_main->custnum"
- if scalar(@$pcustomer) > 1;
-
-# #kinda big question/expensive
-# $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
-# ['Network Default Gateway Address'],
-# [$svc->addr_block->ip_gateway],
-# ['='],
-# );
-# return $err_or_som
-# unless ref($err_or_som);
-#
-# return "No elements in network" unless exists $err_or_som->result->[0];
-
- my $networkid = 0;
-# for (my $i = 0; $i < $err_or_som->result->[0]->attributeNames; $i++) {
-# if ($err_or_som->result->[0]->attributeNames->[$i] eq "Network.ID"){
-# $networkid = $err_or_som->result->[0]->attributeValues->[$i];
-# last;
-# }
-# }
-
- my $element_name_length = 50;
- $element_name_length = $1
- if $self->option('element_name_length') =~ /^\s*(\d+)\s*$/;
- $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement',
- $networkid,
- $svc->mac_addr,
- substr($name . " " . $svc->description,
- 0, $element_name_length),
- $location,
- $contact,
- sprintf("%032X", $svc->authkey),
- $svc->cust_svc->cust_pkg->part_pkg->pkg,
- $svc->vlan_profile,
- ($self->option('ems') ? 1 : 0 ),
- );
- return $err_or_som
- unless ref($err_or_som);
-
- my (@names) = ('Management IP',
- 'GPS Latitude',
- 'GPS Longitude',
- 'GPS Altitude',
- 'Site Name',
- 'Site Location',
- 'Site Contact',
- );
- my (@values) = ($svc->ip_addr,
- $svc->latitude,
- $svc->longitude,
- $svc->altitude,
- $name . " " . $svc->description,
- $location,
- $contact,
- );
- $element = $err_or_som->result->elementId;
- $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
- [ $element ],
- \@names,
- \@values,
- 0,
- 1,
- );
- return $err_or_som
- unless ref($err_or_som);
-
- $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
- [ $element ],
- $svc->vlan_profile,
- 0,
- 1,
- );
- return $err_or_som
- unless ref($err_or_som);
-
- $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
- [ $element ],
- $svc->cust_svc->cust_pkg->part_pkg->pkg,
- 0,
- 1,
- );
- return $err_or_som
- unless ref($err_or_som);
-
- $err_or_som = $self->prizm_command('NetworkIfService',
- 'activateNetworkElements',
- [ $element ],
- 1,
- ( $self->option('ems') ? 1 : 0 ),
- );
-
- return $err_or_som
- unless ref($err_or_som);
-
- $err_or_som = $self->prizm_command('CustomerIfService',
- 'addElementToCustomer',
- 0,
- $cust_main->custnum,
- 0,
- $svc->mac_addr,
- );
-
- return $err_or_som
- unless ref($err_or_som);
-
- '';
-}
-
-sub _export_delete {
- my( $self, $svc ) = ( shift, shift );
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $cust_pkg = $svc->cust_svc->cust_pkg;
-
- my $depend = [];
-
- if ($cust_pkg) {
- my $queue = new FS::queue {
- 'svcnum' => $svc->svcnum,
- 'job' => 'FS::part_export::prizm::queued_prizm_command',
- };
- my $error = $queue->insert(
- ( map { $self->option($_) }
- qw( url user password ) ),
- 'CustomerIfService',
- 'removeElementFromCustomer',
- 0,
- $cust_pkg->custnum,
- 0,
- $svc->mac_addr,
- );
-
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- push @$depend, $queue->jobnum;
- }
-
- my $err_or_queue =
- $self->queue_statuschange('deleteElement', $depend, $svc, 1);
-
- unless (ref($err_or_queue)) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = ( shift, shift, shift );
-
- my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
- [ 'MAC Address' ],
- [ $old->mac_addr ],
- [ '=' ],
- );
- return $err_or_som
- unless ref($err_or_som);
-
- return "Can't find prizm element for " . $old->mac_addr
- unless $err_or_som->result->[0];
-
- my %freeside2prizm = ( mac_addr => 'MAC Address',
- ip_addr => 'Management IP',
- latitude => 'GPS Latitude',
- longitude => 'GPS Longitude',
- altitude => 'GPS Altitude',
- authkey => 'Authentication Key',
- );
-
- my (@values);
- my (@names) = map { push @values, $new->$_; $freeside2prizm{$_} }
- grep { $old->$_ ne $new->$_ }
- grep { exists($freeside2prizm{$_}) }
- fields( 'svc_broadband' );
-
- if ($old->description ne $new->description) {
- my $cust_main = $old->cust_svc->cust_pkg->cust_main;
- my $name = defined($cust_main->dbdef_table->column('ship_last'))
- ? $cust_main->ship_name
- : $cust_main->name;
- push @values, $name . " " . $new->description;
- push @names, "Site Name";
- }
-
- my $element = $err_or_som->result->[0]->elementId;
-
- $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
- [ $element ],
- \@names,
- \@values,
- 0,
- 1,
- );
- return $err_or_som
- unless ref($err_or_som);
-
- $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
- [ $element ],
- $new->vlan_profile,
- 0,
- 1,
- )
- if $old->vlan_profile ne $new->vlan_profile;
-
- return $err_or_som
- unless ref($err_or_som);
-
- '';
-
-}
-
-sub _export_suspend {
- my( $self, $svc ) = ( shift, shift );
- my $depend = [];
- my $ems = $self->option('ems') ? 1 : 0;
- my $err_or_queue = '';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $err_or_queue =
- $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
- unless (ref($err_or_queue)) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- push @$depend, $err_or_queue->jobnum;
-
- if ($ems && $self->option('always_bam')) {
- $err_or_queue =
- $self->queue_statuschange('suspendNetworkElements', $depend, $svc, 1, 0);
- unless (ref($err_or_queue)) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-sub _export_unsuspend {
- my( $self, $svc ) = ( shift, shift );
- my $depend = [];
- my $ems = $self->option('ems') ? 1 : 0;
- my $err_or_queue = '';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ($ems && $self->option('always_bam')) {
- $err_or_queue =
- $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
- unless (ref($err_or_queue)) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- push @$depend, $err_or_queue->jobnum;
- }
-
- $err_or_queue =
- $self->queue_statuschange('activateNetworkElements', $depend, $svc, 1, $ems);
- unless (ref($err_or_queue)) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-sub export_links {
- my( $self, $svc, $arrayref ) = ( shift, shift, shift );
-
- push @$arrayref, '<A HREF="http://'. $svc->ip_addr. '">SM</A>';
-
- '';
-}
-
-sub queue_statuschange {
- my( $self, $method, $jobs, $svc, @args ) = @_;
-
- # already in a transaction and can't die here
-
- my $queue = new FS::queue {
- 'svcnum' => $svc->svcnum,
- 'job' => 'FS::part_export::prizm::statuschange',
- };
- my $error = $queue->insert(
- ( map { $self->option($_) }
- qw( url user password ) ),
- $method,
- $svc->mac_addr,
- @args,
- );
-
- unless ($error) { # successful insertion
- foreach my $job ( @$jobs ) {
- $error ||= $queue->depend_insert($job);
- }
- }
-
- $error or $queue;
-}
-
-sub statuschange { # subroutine
- my( $url, $user, $password, $method, $mac_addr, @args) = @_;
-
- eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
- die $@ if $@;
-
- my $prizm = new Net::Prizm (
- namespace => 'NetworkIfService',
- url => $url,
- user => $user,
- password => $password,
- );
-
- my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
- [ $mac_addr ],
- [ '=' ],
- );
- die $err_or_som
- unless ref($err_or_som);
-
- die "Can't find prizm element for " . $mac_addr
- unless $err_or_som->result->[0];
-
- my $arg1;
- # yuck!
- if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
- $arg1 = [ $err_or_som->result->[0]->elementId ];
- }else{
- $arg1 = $err_or_som->result->[0]->elementId;
- }
- $err_or_som = $prizm->$method( $arg1, @args );
-
- die $err_or_som
- unless ref($err_or_som);
-
- '';
-
-}
-
-
-1;
diff --git a/FS/FS/part_export/radiator.pm b/FS/FS/part_export/radiator.pm
deleted file mode 100644
index 2ac3edb..0000000
--- a/FS/FS/part_export/radiator.pm
+++ /dev/null
@@ -1,167 +0,0 @@
-package FS::part_export::radiator;
-
-use vars qw(@ISA %info $radusers);
-use Tie::IxHash;
-use FS::part_export::sqlradius;
-
-tie my %options, 'Tie::IxHash', %FS::part_export::sqlradius::options;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to RADIATOR',
- 'options' => \%options,
- 'nodomain' => '',
- 'notes' => <<'END',
-Real-time export of the <b>radusers</b> table to any SQL database in
-<a href="http://www.open.com.au/radiator/">Radiator</a>-native format.
-To setup accounting, see the RADIATOR documentation for hooks to update
-a standard <b>radacct</b> table.
-END
-);
-
-@ISA = qw(FS::part_export::sqlradius); #for regular sqlradius accounting
-
-$radusers = 'RADUSERS'; #MySQL is case sensitive about table names! huh
-
-#sub export_username {
-# my($self, $svc_acct) = (shift, shift);
-# $svc_acct->email;
-#}
-
-sub _export_insert {
- my( $self, $svc_acct ) = (shift, shift);
-
- $self->radiator_queue(
- $svc_acct->svcnum,
- 'insert',
- $self->_radiator_hash($svc_acct),
- );
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
-
-# return "can't (yet) change domain with radiator export"
-# if $old->domain ne $new->domain;
-# return "can't (yet) change username with radiator export"
-# if $old->username ne $new->username;
-
- $self->radiator_queue(
- $new->svcnum,
- 'replace',
- $self->export_username($old),
- $self->_radiator_hash($new),
- );
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
-
- $self->radiator_queue(
- $svc_acct->svcnum,
- 'delete',
- $self->export_username($svc_acct),
- );
-}
-
-sub _radiator_hash {
- my( $self, $svc_acct ) = @_;
- my %hash = (
- 'username' => $self->export_username($svc_acct),
- 'pass_word' => $svc_acct->crypt_password,
- 'fullname' => $svc_acct->finger,
- map { my $method = "radius_$_"; $_ => $svc_acct->$method(); }
- qw( framed_filter_id framed_mtu framed_netmask framed_protocol
- framed_routing login_host login_service login_tcp_port )
- );
- $hash{'timeleft'} = $svc_acct->seconds
- if $svc_acct->seconds =~ /^\d+$/;
- $hash{'staticaddress'} = $svc_acct->slipip
- if $svc_acct->slipip =~ /^[\d\.]+$/; # and $self->slipip ne '0.0.0.0';
-
- $hash{'servicename'} = ( $svc_acct->radius_groups )[0];
-
- my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
- $hash{'validto'} = $cust_pkg->bill
- if $cust_pkg && $cust_pkg->part_pkg->is_prepaid && $cust_pkg->bill;
-
- #some other random stuff, should probably be attributes or virtual fields
- #$hash{'state'} = 0; #only inserts
- #$hash{'badlogins'} = 0; #only inserts
- $hash{'maxlogins'} = 1;
- $hash{'addeddate'} = $cust_pkg->setup
- if $cust_pkg && $cust_pkg->setup;
- $hash{'validfrom'} = $cust_pkg->last_bill || $cust_pkg->setup
- if $cust_pkg && ( $cust_pkg->last_bill || $cust_pkg->setup );
- $hash{'state'} = $cust_pkg->susp ? 1 : 0
- if $cust_pkg;
-
- %hash;
-}
-
-sub radiator_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::radiator::radiator_$method",
- };
- $queue->insert(
- $self->option('datasrc'),
- $self->option('username'),
- $self->option('password'),
- @_,
- ); # or $queue;
-}
-
-sub radiator_insert { #subroutine, not method
- my $dbh = radiator_connect(shift, shift, shift);
- my %hash = @_;
- $hash{'state'} = 0; #see "random stuff" above
- $hash{'badlogins'} = 0; #see "random stuff" above
-
- my $sth = $dbh->prepare(
- "INSERT INTO $radusers ( ". join(', ', keys %hash ). ' ) '.
- 'VALUES ( '. join(', ', map '?', keys %hash ). ' ) '
- ) or die $dbh->errstr;
- $sth->execute( values %hash )
- or die $sth->errstr;
-
- $dbh->disconnect;
-
-}
-
-sub radiator_replace { #subroutine, not method
- my $dbh = radiator_connect(shift, shift, shift);
- my ( $old_username, %hash ) = @_;
-
- my $sth = $dbh->prepare(
- "UPDATE $radusers SET ". join(', ', map " $_ = ?", keys %hash ).
- ' WHERE username = ?'
- ) or die $dbh->errstr;
- $sth->execute( values(%hash), $old_username )
- or die $sth->errstr;
-
- $dbh->disconnect;
-}
-
-sub radiator_delete { #subroutine, not method
- my $dbh = radiator_connect(shift, shift, shift);
- my ( $username ) = @_;
-
- my $sth = $dbh->prepare(
- "DELETE FROM $radusers WHERE username = ?"
- ) or die $dbh->errstr;
- $sth->execute( $username )
- or die $sth->errstr;
-
- $dbh->disconnect;
-}
-
-
-sub radiator_connect {
- #my($datasrc, $username, $password) = @_;
- #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
- DBI->connect(@_) or die $DBI::errstr;
-}
-
-1;
diff --git a/FS/FS/part_export/router.pm b/FS/FS/part_export/router.pm
deleted file mode 100644
index 42aa51c..0000000
--- a/FS/FS/part_export/router.pm
+++ /dev/null
@@ -1,375 +0,0 @@
-package FS::part_export::router;
-
-=head1 FS::part_export::router
-
-This export connects to a router and transmits commands via telnet or SSH.
-It requires the following custom router fields:
-
-=head1 Required custom fields
-
-=over 4
-
-=item admin_address - IP address (or hostname) to connect.
-
-=item admin_user - Username for the router.
-
-=item admin_password - Password for the router.
-
-=item admin_protocol - Protocol to use for the router. 'telnet' or 'ssh'. The ssh protocol only support password-less (ie. RSA key) authentication. As such, the admin_password field isn't used if ssh is specified.
-
-=item admin_timeout - Time in seconds to wait for a connection.
-
-=item admin_prompt - A regular expression matching the router's prompt. See Net::Telnet for details. Only applies to the 'telnet' protocol.
-
-=item admin_cmd_insert - Insert export command.
-
-=item admin_cmd_insert_error - Insert export command error pattern.
-
-=item admin_cmd_delete - Delete export command.
-
-=item admin_cmd_delete_error - Delete export command error pattern.
-
-=item admin_cmd_replace - Replace export command.
-
-=item admin_cmd_replace_error - Replace export command error pattern.
-
-=item admin_cmd_suspend - Suspend export command.
-
-=item admin_cmd_suspend_error - Support export command error pattern.
-
-=item admin_cmd_unsuspend - Unsuspend export command.
-
-=item admin_cmd_unsuspend_error - Unsuspend export command error pattern.
-
-The admin_cmd_* virtual fields, if set, will be processed in one of two ways. After being expanded, they will be run on the router specified by admin_address using the protocol specified by admin_protocol.
-
-=over 4
-
-=item Text::Template
-
-If the export command contains the string [@--, then it will be processed with Text::Template using [@-- and --@] as delimeters.
-
-=item eval
-
-If the export command does not contain [@--, it will be double quoted and eval'd.
-
-=back
-
-The admin_cmd_*_error virtual fields, if set, define a regular expression that will be matched against the output of the command being run. If the pattern matches, an error will be raised using the output as the error.
-
-If any of the required router virtual fields are not defined, then the export silently declines.
-
-=back
-
-The export itself takes no options.
-
-=cut
-
-use strict;
-use vars qw(@ISA %info $me $DEBUG);
-use Tie::IxHash;
-use Text::Template;
-
-use FS::Record qw(qsearchs);
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'protocol' => {
- label=>'Protocol',
- type =>'select',
- options => [qw(telnet ssh)],
- default => 'telnet'},
-;
-
-%info = (
- 'svc' => 'svc_broadband',
- 'desc' => 'Send a command to a router.',
- 'options' => \%options,
- 'notes' => 'Installation of Net::Telnet from CPAN is required for telnet connections. This export will execute if the following virtual fields are set on the router: admin_user, admin_password, admin_address, admin_timeout, admin_prompt. Option virtual fields are: admin_cmd_insert, admin_cmd_replace, admin_cmd_delete, admin_cmd_suspend, admin_cmd_unsuspend. See the module documentation for a full list of required/supported router virtual fields.',
-);
-
-$me = '[' . __PACKAGE__ . ']';
-$DEBUG = 1;
-
-
-sub rebless { shift; }
-
-sub _field_prefix { 'admin'; }
-
-sub _req_router_fields {
- map {
- $_[0]->_field_prefix . '_' . $_
- } (qw(address prompt user));
-}
-
-sub _export_insert {
- my($self) = shift;
- warn "Running insert for " . ref($self);
- $self->_export_command('insert', @_);
-}
-
-sub _export_delete {
- my($self) = shift;
- $self->_export_command('delete', @_);
-}
-
-sub _export_suspend {
- my($self) = shift;
- $self->_export_command('suspend', @_);
-}
-
-sub _export_unsuspend {
- my($self) = shift;
- $self->_export_command('unsuspend', @_);
-}
-
-sub _export_replace {
- my($self) = shift;
- $self->_export_command('replace', @_);
-}
-
-sub _export_command {
- my ($self, $action, $svc_broadband) = (shift, shift, shift);
- my ($error, $old);
-
- if ($action eq 'replace') {
- $old = shift;
- }
-
- warn "[debug]$me Processing action '$action'" if $DEBUG;
-
- # fetch router info
- my $router = $self->_get_router($svc_broadband, @_);
- unless ($router) {
- return "Unable to lookup router for $action export";
- }
-
- unless ($self->_check_router_fields($router)) {
- # Virtual fields aren't defined. Exit silently.
- warn "[debug]$me Required router virtual fields not defined. Returning..."
- if $DEBUG;
- return '';
- }
-
- my $args;
- ($error, $args) = $self->_prepare_args(
- $action,
- $router,
- $svc_broadband,
- ($old ? $old : ()),
- @_
- );
-
- if ($error) {
- # Error occured while preparing args.
- return $error;
- } elsif (not defined $args) {
- # Silently decline.
- warn "[debug]$me Declining '$action' export" if $DEBUG;
- return '';
- } # else ... queue the export.
-
- warn "[debug]$me Queueing with args: " . join(', ', @$args) if $DEBUG;
-
- return(
- $self->_queue(
- $svc_broadband->svcnum,
- $self->_get_cmd_sub($svc_broadband, $router),
- @$args
- )
- );
-
-}
-
-sub _prepare_args {
-
- my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
- my $old = shift if ($action eq 'replace');
- my $error = '';
-
- my $field_prefix = $self->_field_prefix;
- my $command = $router->getfield("${field_prefix}_cmd_${action}");
- unless ($command) {
- warn "[debug]$me router custom field '${field_prefix}_cmd_$action' "
- . "is not defined." if $DEBUG;
- return '';
- }
-
- if ($command =~ /\[\@--/) { # Use Text::Template
-
- my $template_data = {};
-
- if ($action eq 'replace') {
- $template_data->{"old_$_"} = $old->getfield($_) foreach $old->fields;
- $template_data->{"new_$_"} = $svc_broadband->getfield($_)
- foreach $svc_broadband->fields;
- } else {
- $template_data->{$_} = $svc_broadband->getfield($_)
- foreach $svc_broadband->fields;
- }
-
- my $template = new Text::Template (
- TYPE => 'STRING',
- SOURCE => $command,
- DELIMITERS => [ '[@--', '--@]' ],
- ) or return "Unable to construct template for router command: "
- . $Text::Template::ERROR;
-
- $command = $template->fill_in(
- HASH => $template_data,
- BROKEN_ARG => \$error,
- BROKEN => sub {
- my %bargs = @_;
- my $err = $bargs{'arg'};
- $$err = $bargs{'error'};
- return undef;
- },
- );
-
- if (not defined $command or $error) {
- $error ||= $Text::Template::ERROR;
- return "Unable to fill-in template for router command: $error";
- }
-
- } else { # Use eval
- no strict 'vars';
- no strict 'refs';
-
- if ($action eq 'replace') {
- ${"old_$_"} = $old->getfield($_) foreach $old->fields;
- ${"new_$_"} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
- $command = eval(qq("$command"));
- } else {
- ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
- $command = eval(qq("$command"));
- }
- return $@ if $@;
- }
-
- my $args = [
- 'user' => $router->getfield($field_prefix . '_user'),
- 'password' => $router->getfield($field_prefix . '_password'),
- 'host' => $router->getfield($field_prefix . '_address'),
- 'Timeout' => $router->getfield($field_prefix . '_timeout'),
- 'Prompt' => $router->getfield($field_prefix . '_prompt'),
- 'command' => $command,
- ];
-
- my $error_check = $router->getfield("${field_prefix}_cmd_${action}_error");
- push(@$args, ('error_check' => $error_check)) if ($error_check);
-
- return('', $args);
-
-}
-
-sub _get_cmd_sub {
-
- my ($self, $svc_broadband, $router) = (shift, shift, shift);
-
- my $protocol = (
- $router->getfield($self->_field_prefix . '_protocol') =~ /^(telnet|ssh)$/
- ) ? $1 : 'telnet';
-
- return(ref($self)."::".$protocol."_cmd");
-
-}
-
-sub _check_router_fields {
-
- my ($self, $router, $action) = (shift, shift, shift);
- my @check_fields = $self->_req_router_fields;
-
- foreach (@check_fields) {
- if ($router->getfield($_) eq '') {
- warn "[debug]$me Required field '$_' is unset" if $DEBUG;
- return 0;
- } else {
- return 1;
- }
- }
-
-}
-
-sub _queue {
- my( $self, $svcnum, $cmd_sub ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- };
- $queue->job($cmd_sub);
- $queue->insert(@_);
-}
-
-sub _get_router {
- my ($self, $svc_broadband, %args) = (shift, shift, shift, @_);
-
- my $router;
- if ($args{'routernum'}) {
- $router = qsearchs('router', { routernum => $args{'routernum'}});
- } else {
- $router = $svc_broadband->addr_block->router;
- }
-
- return($router);
-
-}
-
-
-# Subroutines
-sub ssh_cmd {
- my %arg = @_;
-
- eval 'use Net::SSH \'0.08\'';
- die $@ if $@;
-
- my @out = &Net::SSH::ssh_cmd( { @_ } );
- my $error = &_cmd_error_check(\%arg, \@out);
-
- die ("Error while processing ssh command: $error") if $error;
-
- return '';
-
-}
-
-sub telnet_cmd {
- my %arg = @_;
-
- eval 'use Net::Telnet';
- die $@ if $@;
-
- my $t = new Net::Telnet (Timeout => $arg{'Timeout'},
- Prompt => $arg{'Prompt'});
- $t->open($arg{'host'});
- $t->login($arg{'user'}, $arg{'password'});
- my @out = $t->cmd($arg{'command'});
- my $error = &_cmd_error_check(\%arg, \@out);
-
- die ("Error while processing telnet command: $error") if $error;
-
- return '';
-
-}
-
-sub _cmd_error_check {
- my ($arg, $out) = (shift, shift);
-
- die "_cmd_error_check called without proper arguments"
- unless (ref($arg) eq 'HASH' and ref($out) eq 'ARRAY');
-
- unless (exists($arg->{'error_check'}) and $arg->{'error_check'} ne '') {
- #Preserve default behaviour and return output if a check isn't defined.
- warn "Output from router command: " . join('', @$out) if $DEBUG;
- return '';
- }
-
- my $error_check = $arg->{'error_check'};
- foreach (@$out) {
- return $_ if /$error_check/;
- }
-
- return '';
-
-}
-
-1;
diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm
deleted file mode 100644
index 29e0a57..0000000
--- a/FS/FS/part_export/shellcommands.pm
+++ /dev/null
@@ -1,399 +0,0 @@
-package FS::part_export::shellcommands;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use String::ShellQuote;
-use FS::part_export;
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote username', default=>'root' },
- 'useradd' => { label=>'Insert command',
- default=>'useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username'
- #default=>'cp -pr /etc/skel $dir; chown -R $uid.$gid $dir'
- },
- 'useradd_stdin' => { label=>'Insert command STDIN',
- type =>'textarea',
- default=>'',
- },
- 'userdel' => { label=>'Delete command',
- default=>'userdel -r $username',
- #default=>'rm -rf $dir',
- },
- 'userdel_stdin' => { label=>'Delete command STDIN',
- type =>'textarea',
- default=>'',
- },
- 'usermod' => { label=>'Modify command',
- default=>'usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -p $new_crypt_password $old_username',
- #default=>'[ -d $old_dir ] && mv $old_dir $new_dir || ( '.
- # 'chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; '.
- # 'find . -depth -print | cpio -pdm $new_dir; '.
- # 'chmod u-t $new_dir; chown -R $uid.$gid $new_dir; '.
- # 'rm -rf $old_dir'.
- #')'
- },
- 'usermod_stdin' => { label=>'Modify command STDIN',
- type =>'textarea',
- default=>'',
- },
- 'usermod_pwonly' => { label=>'Disallow username, domain, uid, gid, and dir changes', #and RADIUS group changes',
- type =>'checkbox',
- },
- 'usermod_nousername' => { label=>'Disallow just username changes',
- type =>'checkbox',
- },
- 'suspend' => { label=>'Suspension command',
- default=>'usermod -L $username',
- },
- 'suspend_stdin' => { label=>'Suspension command STDIN',
- default=>'',
- },
- 'unsuspend' => { label=>'Unsuspension command',
- default=>'usermod -U $username',
- },
- 'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
- default=>'',
- },
- 'crypt' => { label => 'Default password encryption',
- type=>'select', options=>[qw(crypt md5)],
- default => 'crypt',
- },
- 'groups_susp_reason' => { label =>
- 'Radius group mapping to reason (via template user)',
- type => 'textarea',
- },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' =>
- 'Real-time export via remote SSH (i.e. useradd, userdel, etc.)',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => <<'END'
-Run remote commands via SSH. Usernames are considered unique (also see
-shellcommands_withdomain). You probably want this if the commands you are
-running will not accept a domain as a parameter. You will need to
-<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
-
-<BR><BR>Use these buttons for some useful presets:
-<UL>
- <LI>
- <INPUT TYPE="button" VALUE="Linux" onClick='
- this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
- this.form.useradd_stdin.value = "";
- this.form.userdel.value = "userdel -r $username";
- this.form.userdel_stdin.value="";
- this.form.usermod.value = "usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -p $new_crypt_password $old_username";
- this.form.usermod_stdin.value = "";
- this.form.suspend.value = "usermod -L $username";
- this.form.suspend_stdin.value="";
- this.form.unsuspend.value = "usermod -U $username";
- this.form.unsuspend_stdin.value="";
- '>
- <LI>
- <INPUT TYPE="button" VALUE="FreeBSD before 4.10 / 5.3" onClick='
- this.form.useradd.value = "lockf /etc/passwd.lock pw useradd $username -d $dir -m -s $shell -u $uid -c $finger -h 0";
- this.form.useradd_stdin.value = "$_password\n";
- this.form.userdel.value = "lockf /etc/passwd.lock pw userdel $username -r"; this.form.userdel_stdin.value="";
- this.form.usermod.value = "lockf /etc/passwd.lock pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -c $new_finger -h 0";
- this.form.usermod_stdin.value = "$new__password\n"; this.form.suspend.value = "lockf /etc/passwd.lock pw lock $username";
- this.form.suspend_stdin.value="";
- this.form.unsuspend.value = "lockf /etc/passwd.lock pw unlock $username"; this.form.unsuspend_stdin.value="";
- '>
- Note: On FreeBSD versions before 5.3 and 4.10 (4.10 is after 4.9, not
- 4.1!), due to deficient locking in pw(1), you must disable the chpass(1),
- chsh(1), chfn(1), passwd(1), and vipw(1) commands, or replace them with
- wrappers that prepend "lockf /etc/passwd.lock". Alternatively, apply the
- patch in
- <A HREF="http://www.freebsd.org/cgi/query-pr.cgi?pr=23501">FreeBSD PR#23501</A>
- and use the "FreeBSD 4.10 / 5.3 or later" button below.
- <LI>
- <INPUT TYPE="button" VALUE="FreeBSD 4.10 / 5.3 or later" onClick='
- this.form.useradd.value = "pw useradd $username -d $dir -m -s $shell -u $uid -g $gid -c $finger -h 0";
- this.form.useradd_stdin.value = "$_password\n";
- this.form.userdel.value = "pw userdel $username -r";
- this.form.userdel_stdin.value="";
- this.form.usermod.value = "pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -c $new_finger -h 0";
- this.form.usermod_stdin.value = "$new__password\n";
- this.form.suspend.value = "pw lock $username";
- this.form.suspend_stdin.value="";
- this.form.unsuspend.value = "pw unlock $username";
- this.form.unsuspend_stdin.value="";
- '>
- <LI>
- <INPUT TYPE="button" VALUE="NetBSD/OpenBSD" onClick='
- this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
- this.form.useradd_stdin.value = "";
- this.form.userdel.value = "userdel -r $username";
- this.form.userdel_stdin.value="";
- this.form.usermod.value = "usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -g $new_gid -p $new_crypt_password $old_username";
- this.form.usermod_stdin.value = "";
- this.form.suspend.value = "";
- this.form.suspend_stdin.value="";
- this.form.unsuspend.value = "";
- this.form.unsuspend_stdin.value="";
- '>
- <LI>
- <INPUT TYPE="button" VALUE="Just maintain directories (use with sysvshell or bsdshell)" onClick='
- this.form.useradd.value = "cp -pr /etc/skel $dir; chown -R $uid.$gid $dir"; this.form.useradd_stdin.value = "";
- this.form.usermod.value = "[ -d $old_dir ] && mv $old_dir $new_dir || ( chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; find . -depth -print | cpio -pdm $new_dir; chmod u-t $new_dir; chown -R $new_uid.$new_gid $new_dir; rm -rf $old_dir )";
- this.form.usermod_stdin.value = "";
- this.form.userdel.value = "rm -rf $dir";
- this.form.userdel_stdin.value="";
- this.form.suspend.value = "";
- this.form.suspend_stdin.value="";
- this.form.unsuspend.value = "";
- this.form.unsuspend_stdin.value="";
- '>
-</UL>
-
-The following variables are available for interpolation (prefixed with new_ or
-old_ for replace operations):
-<UL>
- <LI><code>$username</code>
- <LI><code>$_password</code>
- <LI><code>$quoted_password</code> - unencrypted password, already quoted for the shell (do not add additional quotes).
- <LI><code>$crypt_password</code> - encrypted password. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
- <LI><code>$ldap_password</code> - Password in LDAP/RFC2307 format (for example, "{PLAIN}himom", "{CRYPT}94pAVyK/4oIBk" or "{MD5}5426824942db4253f87a1009fd5d2d4"). When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
- <LI><code>$uid</code>
- <LI><code>$gid</code>
- <LI><code>$finger</code> - GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
- <LI><code>$first</code> - First name of GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
- <LI><code>$last</code> - Last name of GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
- <LI><code>$dir</code> - home directory
- <LI><code>$shell</code>
- <LI><code>$quota</code>
- <LI><code>@radius_groups</code>
- <LI><code>$reasonnum (when suspending)</code>
- <LI><code>$reasontext (when suspending)</code>
- <LI><code>$reasontypenum (when suspending)</code>
- <LI><code>$reasontypetext (when suspending)</code>
- <LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.
-</UL>
-END
-);
-
-sub _groups_susp_reason_map { shift->_map('groups_susp_reason'); }
-
-sub _map {
- my $self = shift;
- map { reverse(/^\s*(\S+)\s*(.*)\s*$/) } split("\n", $self->option(shift) );
-}
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self) = shift;
- $self->_export_command('useradd', @_);
-}
-
-sub _export_delete {
- my($self) = shift;
- $self->_export_command('userdel', @_);
-}
-
-sub _export_suspend {
- my($self) = shift;
- $self->_export_command_or_super('suspend', @_);
-}
-
-sub _export_unsuspend {
- my($self) = shift;
- $self->_export_command_or_super('unsuspend', @_);
-}
-
-sub _export_command_or_super {
- my($self, $action) = (shift, shift);
- if ( $self->option($action) =~ /^\s*$/ ) {
- my $method = "SUPER::_export_$action";
- $self->$method(@_);
- } else {
- $self->_export_command($action, @_);
- }
-};
-
-sub _export_command {
- my ( $self, $action, $svc_acct) = (shift, shift, shift);
- my $command = $self->option($action);
- return '' if $command =~ /^\s*$/;
- my $stdin = $self->option($action."_stdin");
-
- no strict 'vars';
- {
- no strict 'refs';
- ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields;
-
- # snarfs are unused at this point?
- my $count = 1;
- foreach my $acct_snarf ( $svc_acct->acct_snarf ) {
- ${"snarf_$_$count"} = shell_quote( $acct_snarf->get($_) )
- foreach qw( machine username _password );
- $count++;
- }
- }
-
- my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
- if ( $cust_pkg ) {
- $email = ( grep { $_ !~ /^(POST|FAX)$/ } $cust_pkg->cust_main->invoicing_list )[0];
- } else {
- $email = '';
- }
-
- $finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/;
- ($first, $last ) = ( $1, $2 );
- $domain = $svc_acct->domain;
-
- $quoted_password = shell_quote $_password;
-
- $crypt_password = $svc_acct->crypt_password( $self->option('crypt') );
- $ldap_password = $svc_acct->ldap_password( $self->option('crypt') );
-
- @radius_groups = $svc_acct->radius_groups;
-
- my ($reasonnum, $reasontext, $reasontypenum, $reasontypetext);
- if ( $cust_pkg && $action eq 'suspend' && (my $r = $cust_pkg->last_reason) ) {
- $reasonnum = $r->reasonnum;
- $reasontext = $r->reason;
- $reasontypenum = $r->reason_type;
- $reasontypetext = $r->reasontype->type;
-
- my %reasonmap = $self->_groups_susp_reason_map;
- my $userspec = '';
- $userspec = $reasonmap{$reasonnum}
- if exists($reasonmap{$reasonnum});
- $userspec = $reasonmap{$reasontext}
- if (!$userspec && exists($reasonmap{$reasontext}));
-
- my $suspend_user;
- if ( $userspec =~ /^\d+$/ ) {
- $suspend_user = qsearchs( 'svc_acct', { 'svcnum' => $userspec } );
- } elsif ( $userspec =~ /^\S+\@\S+$/ ) {
- my ($username,$domain) = split(/\@/, $userspec);
- for my $user (qsearch( 'svc_acct', { 'username' => $username } )){
- $suspend_user = $user if $userspec eq $user->email;
- }
- } elsif ($userspec) {
- $suspend_user = qsearchs( 'svc_acct', { 'username' => $userspec } );
- }
-
- @radius_groups = $suspend_user->radius_groups
- if $suspend_user;
-
- } else {
- $reasonnum = $reasontext = $reasontypenum = $reasontypetext = '';
- }
-
- my $stdin_string = eval(qq("$stdin"));
-
- $first = shell_quote $first;
- $last = shell_quote $last;
- $finger = shell_quote $finger;
- $crypt_password = shell_quote $crypt_password;
- $ldap_password = shell_quote $ldap_password;
-
- my $command_string = eval(qq("$command"));
-
- $self->shellcommands_queue( $svc_acct->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => $command_string,
- stdin_string => $stdin_string,
- );
-}
-
-sub _export_replace {
- my($self, $new, $old ) = (shift, shift, shift);
- my $command = $self->option('usermod');
- my $stdin = $self->option('usermod_stdin');
- no strict 'vars';
- {
- no strict 'refs';
- ${"old_$_"} = $old->getfield($_) foreach $old->fields;
- ${"new_$_"} = $new->getfield($_) foreach $new->fields;
- }
- $new_finger =~ /^(.*)\s+(\S+)$/ or $new_finger =~ /^((.*))$/;
- ($new_first, $new_last ) = ( $1, $2 );
- $quoted_new__password = shell_quote $new__password; #old, wrong?
- $new_quoted_password = shell_quote $new__password; #new, better?
- $old_domain = $old->domain;
- $new_domain = $new->domain;
-
- $new_crypt_password = $new->crypt_password( $self->option('crypt') );
- $new_ldap_password = $new->ldap_password( $self->option('crypt') );
-
- @old_radius_groups = $old->radius_groups;
- @new_radius_groups = $new->radius_groups;
-
- my $error = '';
- if ( $self->option('usermod_pwonly') || $self->option('usermod_nousername') ){
- if ( $old_username ne $new_username ) {
- $error ||= "can't change username";
- }
- }
- if ( $self->option('usermod_pwonly') ) {
- if ( $old_domain ne $new_domain ) {
- $error ||= "can't change domain";
- }
- if ( $old_uid != $new_uid ) {
- $error ||= "can't change uid";
- }
- if ( $old_gid != $new_gid ) {
- $error ||= "can't change gid";
- }
- if ( $old_dir ne $new_dir ) {
- $error ||= "can't change dir";
- }
- #if ( join("\n", sort @old_radius_groups) ne
- # join("\n", sort @new_radius_groups) ) {
- # $error ||= "can't change RADIUS groups";
- #}
- }
- return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')'
- if $error;
-
- my $stdin_string = eval(qq("$stdin"));
-
- $new_first = shell_quote $new_first;
- $new_last = shell_quote $new_last;
- $new_finger = shell_quote $new_finger;
- $new_crypt_password = shell_quote $new_crypt_password;
- $new_ldap_password = shell_quote $new_ldap_password;
-
- my $command_string = eval(qq("$command"));
-
- $self->shellcommands_queue( $new->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => $command_string,
- stdin_string => $stdin_string,
- );
-}
-
-#a good idea to queue anything that could fail or take any time
-sub shellcommands_queue {
- my( $self, $svcnum ) = (shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::shellcommands::ssh_cmd",
- };
- $queue->insert( @_ );
-}
-
-sub ssh_cmd { #subroutine, not method
- use Net::SSH '0.08';
- &Net::SSH::ssh_cmd( { @_ } );
-}
-
-#sub shellcommands_insert { #subroutine, not method
-#}
-#sub shellcommands_replace { #subroutine, not method
-#}
-#sub shellcommands_delete { #subroutine, not method
-#}
-
-1;
-
diff --git a/FS/FS/part_export/shellcommands_withdomain.pm b/FS/FS/part_export/shellcommands_withdomain.pm
deleted file mode 100644
index 7c5d904..0000000
--- a/FS/FS/part_export/shellcommands_withdomain.pm
+++ /dev/null
@@ -1,112 +0,0 @@
-package FS::part_export::shellcommands_withdomain;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export::shellcommands;
-
-@ISA = qw(FS::part_export::shellcommands);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote username', default=>'root' },
- 'useradd' => { label=>'Insert command',
- #default=>''
- },
- 'useradd_stdin' => { label=>'Insert command STDIN',
- type =>'textarea',
- #default=>"$_password\n$_password\n",
- },
- 'userdel' => { label=>'Delete command',
- #default=>'',
- },
- 'userdel_stdin' => { label=>'Delete command STDIN',
- type =>'textarea',
- #default=>'',
- },
- 'usermod' => { label=>'Modify command',
- default=>'',
- },
- 'usermod_stdin' => { label=>'Modify command STDIN',
- type =>'textarea',
- #default=>"$_password\n$_password\n",
- },
- 'usermod_pwonly' => { label=>'Disallow username, domain, uid, dir and RADIUS group changes',
- type =>'checkbox',
- },
- 'usermod_nousername' => { label=>'Disallow just username changes',
- type =>'checkbox',
- },
- 'suspend' => { label=>'Suspension command',
- default=>'',
- },
- 'suspend_stdin' => { label=>'Suspension command STDIN',
- default=>'',
- },
- 'unsuspend' => { label=>'Unsuspension command',
- default=>'',
- },
- 'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
- default=>'',
- },
- 'crypt' => { label => 'Default password encryption',
- type=>'select', options=>[qw(crypt md5)],
- default => 'crypt',
- },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export via remote SSH (vpopmail, ISPMan)',
- 'options' => \%options,
- 'notes' => <<'END'
-Run remote commands via SSH. username@domain (rather than just usernames) are
-considered unique (also see shellcommands). You probably want this if the
-commands you are running will accept a domain as a parameter, and will allow
-the same username with different domains. You will need to
-<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
-
-<BR><BR>Use these buttons for some useful presets:
-<UL>
- <LI><INPUT TYPE="button" VALUE="vpopmail" onClick='
- this.form.useradd.value = "/home/vpopmail/bin/vadduser $username\\\@$domain $quoted_password";
- this.form.useradd_stdin.value = "";
- this.form.userdel.value = "/home/vpopmail/bin/vdeluser $username\\\@$domain";
- this.form.userdel_stdin.value="";
- this.form.usermod.value = "/home/vpopmail/bin/vpasswd $new_username\\\@$new_domain $new_quoted_password";
- this.form.usermod_stdin.value = "";
- this.form.usermod_pwonly.checked = true;
- '>
- <LI><INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
- this.form.useradd.value = "/usr/local/ispman/bin/ispman.addUser -d $domain -f $first -l $last -q $quota -p $quoted_password $username";
- this.form.useradd_stdin.value = "";
- this.form.userdel.value = "/usr/local/ispman/bin/ispman.delUser -d $domain $username";
- this.form.userdel_stdin.value="";
- this.form.usermod.value = "/usr/local/ispman/bin/ispman.passwd.user $new_username\\\@$new_domain $new_quoted_password";
- this.form.usermod_stdin.value = "";
- this.form.usermod_pwonly.checked = true;
- '>
-</UL>
-
-The following variables are available for interpolation (prefixed with
-<code>new_</code> or <code>old_</code> for replace operations):
-<UL>
- <LI><code>$username</code>
- <LI><code>$domain</code>
- <LI><code>$_password</code>
- <LI><code>$quoted_password</code> - unencrypted password, already quoted for the shell (do not add additional quotes)
- <LI><code>$crypt_password</code> - encrypted password, already quoted for the shell (do not add additional quotes)
- <LI><code>$uid</code>
- <LI><code>$gid</code>
- <LI><code>$finger</code> - GECOS, already quoted for the shell (do not add additional quotes)
- <LI><code>$first</code> - First name of GECOS, already quoted for the shell (do not add additional quotes)
- <LI><code>$last</code> - Last name of GECOS, already quoted for the shell (do not add additional quotes)
- <LI><code>$dir</code> - home directory
- <LI><code>$shell</code>
- <LI><code>$quota</code>
- <LI><code>@radius_groups</code>
- <LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.
-</UL>
-END
-);
-
-1;
-
diff --git a/FS/FS/part_export/snmp.pm b/FS/FS/part_export/snmp.pm
deleted file mode 100644
index 81b3c7e..0000000
--- a/FS/FS/part_export/snmp.pm
+++ /dev/null
@@ -1,256 +0,0 @@
-package FS::part_export::snmp;
-
-=head1 FS::part_export::snmp
-
-This export sends SNMP SETs to a router using the Net::SNMP package. It requires the following custom fields to be defined on a router. If any of the required custom fields are not present, then the export will exit quietly.
-
-=head1 Required custom fields
-
-=over 4
-
-=item snmp_address - IP address (or hostname) of the router/agent
-
-=item snmp_comm - R/W SNMP community of the router/agent
-
-=item snmp_version - SNMP version of the router/agent
-
-=back
-
-=head1 Optional custom fields
-
-=over 4
-
-=item snmp_cmd_insert - SNMP SETs to perform on insert. See L</Formatting>
-
-=item snmp_cmd_replace - SNMP SETs to perform on replace. See L</Formatting>
-
-=item snmp_cmd_delete - SNMP SETs to perform on delete. See L</Formatting>
-
-=item snmp_cmd_suspend - SNMP SETs to perform on suspend. See L</Formatting>
-
-=item snmp_cmd_unsuspend - SNMP SETs to perform on unsuspend. See L</Formatting>
-
-=back
-
-=head1 Formatting
-
-The values for the snmp_cmd_* fields should be formatted as follows:
-
-<OID>|<Data Type>|<expr>[||<OID>|<Data Type>|<expr>[...]]
-
-=over 4
-
-=item OID - SNMP object ID (ex. 1.3.6.1.4.1.1.20). If the OID string starts with a '.', then the Private Enterprise OID (1.3.6.1.4.1) is prepended.
-
-=item Data Type - SNMP data types understood by L<Net::SNMP>, as well as HEX_STRING for convenience. ex. INTEGER, OCTET_STRING, IPADDRESS, ...
-
-=item expr - Expression to be eval'd by freeside. By default, the expression is double quoted and eval'd with all FS::svc_broadband fields available as scalars (ex. $svcnum, $ip_addr, $speed_up). However, if the expression contains a non-escaped double quote, the expression is eval'd without being double quoted. In this case, the expression must be a block of valid perl code that returns the desired value.
-
-You must escape non-delimiter pipes ("|") with a backslash.
-
-=back
-
-=head1 Examples
-
-This is an example for exporting to a Trango Access5830 AP. Newlines inserted for clarity.
-
-=over 4
-
-=item snmp_cmd_delete -
-
-1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
-1.3.6.1.4.1.5454.1.20.3.5.8|INTEGER|1|
-
-=item snmp_cmd_insert -
-
-1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
-1.3.6.1.4.1.5454.1.20.3.5.2|HEX_STRING|join("",$radio_addr =~ /[0-9a-fA-F]{2}/g)||
-1.3.6.1.4.1.5454.1.20.3.5.7|INTEGER|1|
-
-=item snmp_cmd_replace -
-
-1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
-1.3.6.1.4.1.5454.1.20.3.5.8|INTEGER|1||1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
-1.3.6.1.4.1.5454.1.20.3.5.2|HEX_STRING|join("",$new_radio_addr =~ /[0-9a-fA-F]{2}/g)||
-1.3.6.1.4.1.5454.1.20.3.5.7|INTEGER|1|
-
-=back
-
-=cut
-
-
-use strict;
-use vars qw(@ISA %info $me $DEBUG);
-use Tie::IxHash;
-use FS::Record qw(qsearch qsearchs);
-use FS::part_export;
-use FS::part_export::router;
-
-@ISA = qw(FS::part_export::router);
-
-tie my %options, 'Tie::IxHash', ();
-
-%info = (
- 'svc' => 'svc_broadband',
- 'desc' => 'Sends SNMP SETs to an SNMP agent.',
- 'options' => \%options,
- 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::snmp for required virtual fields and usage information.',
-);
-
-$me= '[' . __PACKAGE__ . ']';
-$DEBUG = 1;
-
-
-sub _field_prefix { 'snmp'; }
-
-sub _req_router_fields {
- map {
- $_[0]->_field_prefix . '_' . $_
- } (qw(address comm version));
-}
-
-sub _get_cmd_sub {
-
- my ($self, $svc_broadband, $router) = (shift, shift, shift);
-
- return(ref($self) . '::snmp_cmd');
-
-}
-
-sub _prepare_args {
-
- my ($self, $action, $router) = (shift, shift, shift);
- my ($svc_broadband) = shift;
- my $old;
- my $field_prefix = $self->_field_prefix;
-
- if ($action eq 'replace') { $old = shift; }
-
- my $raw_cmd = $router->getfield("${field_prefix}_cmd_${action}");
- unless ($raw_cmd) {
- warn "[debug]$me router custom field '${field_prefix}_cmd_$action' "
- . "is not defined." if $DEBUG;
- return '';
- }
-
- my $args = [
- '-hostname' => $router->getfield($field_prefix.'_address'),
- '-version' => $router->getfield($field_prefix.'_version'),
- '-community' => $router->getfield($field_prefix.'_comm'),
- ];
-
- my @varbindlist = ();
-
- foreach my $snmp_cmd ($raw_cmd =~ m/(.*?[^\\])(?:\|\||$)/g) {
-
- warn "[debug]$me snmp_cmd is '$snmp_cmd'" if $DEBUG;
-
- my ($oid, $type, $expr) = $snmp_cmd =~ m/(.*?[^\\])(?:\||$)/g;
-
- if ($oid =~ /^([\d\.]+)$/) {
- $oid = $1;
- $oid = ($oid =~ /^\./) ? '1.3.6.1.4.1' . $oid : $oid;
- } else {
- return "Invalid SNMP OID '$oid'";
- }
-
- if ($type =~ /^([A-Z_\d]+)$/) {
- $type = $1;
- } else {
- return "Invalid SNMP ASN.1 type '$type'";
- }
-
- if ($expr =~ /^(.*)$/) {
- $expr = $1;
- } else {
- return "Invalid expression '$expr'";
- }
-
- {
- no strict 'vars';
- no strict 'refs';
-
- if ($action eq 'replace') {
- ${"old_$_"} = $old->getfield($_) foreach $old->fields;
- ${"new_$_"} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
- $expr = ($expr =~/[^\\]"/) ? eval($expr) : eval(qq("$expr"));
- } else {
- ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
- $expr = ($expr =~/[^\\]"/) ? eval($expr) : eval(qq("$expr"));
- }
- return $@ if $@;
- }
-
- push @varbindlist, ($oid, $type, $expr);
-
- }
-
- push @$args, ('-varbindlist', @varbindlist);
-
- return('', $args);
-
-}
-
-sub snmp_cmd {
- eval "use Net::SNMP;";
- die $@ if $@;
-
- my %args = ();
- my @varbindlist = ();
- while (scalar(@_)) {
- my $key = shift;
- if ($key eq '-varbindlist') {
- push @varbindlist, @_;
- last;
- } else {
- $args{$key} = shift;
- }
- }
-
- my $i = 0;
- while ($i*3 < scalar(@varbindlist)) {
- my $type_index = ($i*3)+1;
- my $type_name = $varbindlist[$type_index];
-
- # Implementing HEX_STRING outselves since Net::SNMP doesn't. Ewwww!
- if ($type_name eq 'HEX_STRING') {
- my $value_index = $type_index + 1;
- $type_name = 'OCTET_STRING';
- $varbindlist[$value_index] = pack('H*', $varbindlist[$value_index]);
- }
-
- my $type = eval "Net::SNMP::$type_name";
- if ($@ or not defined $type) {
- warn $@ if $DEBUG;
- die "snmp_cmd error: Unable to lookup type '$type_name'";
- }
-
- $varbindlist[$type_index] = $type;
- } continue {
- $i++;
- }
-
- my ($snmp, $error) = Net::SNMP->session(%args);
- die "snmp_cmd error: $error" unless($snmp);
-
- my $res = $snmp->set_request('-varbindlist' => \@varbindlist);
- unless($res) {
- $error = $snmp->error;
- $snmp->close;
- die "snmp_cmd error: " . $error;
- }
-
- $snmp->close;
-
- return '';
-
-}
-
-
-=head1 BUGS
-
-Plenty, I'm sure.
-
-=cut
-
-1;
diff --git a/FS/FS/part_export/sqlmail.pm b/FS/FS/part_export/sqlmail.pm
deleted file mode 100644
index cbdaf7f..0000000
--- a/FS/FS/part_export/sqlmail.pm
+++ /dev/null
@@ -1,220 +0,0 @@
-package FS::part_export::sqlmail;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use Digest::MD5 qw(md5_hex);
-use FS::Record qw(qsearchs);
-use FS::part_export;
-use FS::svc_domain;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'datasrc' => { label => 'DBI data source' },
- 'username' => { label => 'Database username' },
- 'password' => { label => 'Database password' },
- 'server_type' => {
- label => 'Server type',
- type => 'select',
- options => [qw(dovecot_plain dovecot_crypt dovecot_digest_md5 courier_plain
- courier_crypt)],
- default => ['dovecot_plain'], },
- 'svc_acct_table' => { label => 'User Table', default => 'user_acct' },
- 'svc_forward_table' => { label => 'Forward Table', default => 'forward' },
- 'svc_domain_table' => { label => 'Domain Table', default => 'domain' },
- 'svc_acct_fields' => { label => 'svc_acct Export Fields',
- default => 'username _password domsvc svcnum' },
- 'svc_forward_fields' => { label => 'svc_forward Export Fields',
- default => 'srcsvc dstsvc dst' },
- 'svc_domain_fields' => { label => 'svc_domain Export Fields',
- default => 'domain svcnum catchall' },
- 'resolve_dstsvc' => { label => q{Resolve svc_forward.dstsvc to an email address and store it in dst. (Doesn't require that you also export dstsvc.)},
- type => 'checkbox' },
-;
-
-%info = (
- 'svc' => [qw( svc_acct svc_domain svc_forward )],
- 'desc' => 'Real-time export to SQL-backed mail server',
- 'options' => \%options,
- 'nodomain' => '',
- 'notes' => <<'END'
-Database schema can be made to work with Courier IMAP, Exim and Dovecot.
-Others could work but are untested. (more detailed description from
-Kristian / fire2wire? )
-END
-);
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self, $svc) = (shift, shift);
- # this is a svc_something.
-
- my $svcdb = $svc->cust_svc->part_svc->svcdb;
- my $export_table = $self->option($svcdb . '_table')
- or die('Export table not defined for svcdb: ' . $svcdb);
- my @export_fields = split(/\s+/, $self->option($svcdb . '_fields'));
- my $svchash = update_values($self, $svc, $svcdb);
-
- foreach my $key (keys(%$svchash)) {
- unless (grep { $key eq $_ } @export_fields) {
- delete $svchash->{$key};
- }
- }
-
- my $error = $self->sqlmail_queue( $svc->svcnum, 'insert',
- $self->option('server_type'), $export_table,
- (map { ($_, $svchash->{$_}); } keys(%$svchash)));
- return $error if $error;
- '';
-
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
-
- my $svcdb = $new->cust_svc->part_svc->svcdb;
- my $export_table = $self->option($svcdb . '_table')
- or die('Export table not defined for svcdb: ' . $svcdb);
- my @export_fields = split(/\s+/, $self->option($svcdb . '_fields'));
- my $svchash = update_values($self, $new, $svcdb);
-
- foreach my $key (keys(%$svchash)) {
- unless (grep { $key eq $_ } @export_fields) {
- delete $svchash->{$key};
- }
- }
-
- my $error = $self->sqlmail_queue( $new->svcnum, 'replace',
- $old->svcnum, $self->option('server_type'), $export_table,
- (map { ($_, $svchash->{$_}); } keys(%$svchash)));
- return $error if $error;
- '';
-
-}
-
-sub _export_delete {
- my( $self, $svc ) = (shift, shift);
-
- my $svcdb = $svc->cust_svc->part_svc->svcdb;
- my $table = $self->option($svcdb . '_table')
- or die('Export table not defined for svcdb: ' . $svcdb);
-
- $self->sqlmail_queue( $svc->svcnum, 'delete', $table,
- $svc->svcnum );
-}
-
-sub sqlmail_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::sqlmail::sqlmail_$method",
- };
- $queue->insert(
- $self->option('datasrc'),
- $self->option('username'),
- $self->option('password'),
- @_,
- );
-}
-
-sub sqlmail_insert { #subroutine, not method
- my $dbh = sqlmail_connect(shift, shift, shift);
- my( $server_type, $table ) = (shift, shift);
-
- my %attrs = @_;
-
- map { $attrs{$_} = $attrs{$_} ? qq!'$attrs{$_}'! : 'NULL'; } keys(%attrs);
- my $query = sprintf("INSERT INTO %s (%s) values (%s)",
- $table, join(",", keys(%attrs)),
- join(',', values(%attrs)));
-
- $dbh->do($query) or die $dbh->errstr;
- $dbh->disconnect;
-
- '';
-}
-
-sub sqlmail_delete { #subroutine, not method
- my $dbh = sqlmail_connect(shift, shift, shift);
- my( $table, $svcnum ) = @_;
-
- $dbh->do("DELETE FROM $table WHERE svcnum = $svcnum") or die $dbh->errstr;
- $dbh->disconnect;
-
- '';
-}
-
-sub sqlmail_replace {
- my $dbh = sqlmail_connect(shift, shift, shift);
- my($oldsvcnum, $server_type, $table) = (shift, shift, shift);
-
- my %attrs = @_;
- map { $attrs{$_} = $attrs{$_} ? qq!'$attrs{$_}'! : 'NULL'; } keys(%attrs);
-
- my $query = "SELECT COUNT(*) FROM $table WHERE svcnum = $oldsvcnum";
- my $result = $dbh->selectrow_arrayref($query) or die $dbh->errstr;
-
- if (@$result[0] == 0) {
- $query = sprintf("INSERT INTO %s (%s) values (%s)",
- $table, join(",", keys(%attrs)),
- join(',', values(%attrs)));
- $dbh->do($query) or die $dbh->errstr;
- } else {
- $query = sprintf('UPDATE %s SET %s WHERE svcnum = %s',
- $table, join(', ', map {"$_ = $attrs{$_}"} keys(%attrs)),
- $oldsvcnum);
- $dbh->do($query) or die $dbh->errstr;
- }
-
- $dbh->disconnect;
-
- '';
-}
-
-sub sqlmail_connect {
- DBI->connect(@_) or die $DBI::errstr;
-}
-
-sub update_values {
-
- # Update records to conform to a particular server_type.
-
- my ($self, $svc, $svcdb) = (shift,shift,shift);
- my $svchash = { %{$svc->hashref} } or return ''; # We need a copy.
-
- if ($svcdb eq 'svc_acct') {
- if ($self->option('server_type') eq 'courier_crypt') {
- my $salt = join '', ('.', '/', 0..9,'A'..'Z', 'a'..'z')[rand 64, rand 64];
- $svchash->{_password} = crypt($svchash->{_password}, $salt);
-
- } elsif ($self->option('server_type') eq 'dovecot_plain') {
- $svchash->{_password} = '{PLAIN}' . $svchash->{_password};
-
- } elsif ($self->option('server_type') eq 'dovecot_crypt') {
- my $salt = join '', ('.', '/', 0..9,'A'..'Z', 'a'..'z')[rand 64, rand 64];
- $svchash->{_password} = '{CRYPT}' . crypt($svchash->{_password}, $salt);
-
- } elsif ($self->option('server_type') eq 'dovecot_digest_md5') {
- my $svc_domain = qsearchs('svc_domain', { svcnum => $svc->domsvc });
- die('Unable to lookup svc_domain with domsvc: ' . $svc->domsvc)
- unless ($svc_domain);
-
- my $domain = $svc_domain->domain;
- my $md5hash = '{DIGEST-MD5}' . md5_hex(join(':', $svchash->{username},
- $domain, $svchash->{_password}));
- $svchash->{_password} = $md5hash;
- }
- } elsif ($svcdb eq 'svc_forward') {
- if ($self->option('resolve_dstsvc') && $svc->dstsvc_acct) {
- $svchash->{dst} = $svc->dstsvc_acct->username . '@' .
- $svc->dstsvc_acct->svc_domain->domain;
- }
- }
-
- return($svchash);
-
-}
-
-1;
-
diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm
deleted file mode 100644
index 5e63e10..0000000
--- a/FS/FS/part_export/sqlradius.pm
+++ /dev/null
@@ -1,722 +0,0 @@
-package FS::part_export::sqlradius;
-
-use vars qw(@ISA $DEBUG %info %options $notes1 $notes2);
-use Tie::IxHash;
-use FS::Record qw( dbh qsearch qsearchs str2time_sql );
-use FS::part_export;
-use FS::svc_acct;
-use FS::export_svc;
-use Carp qw( cluck );
-
-@ISA = qw(FS::part_export);
-
-$DEBUG = 0;
-
-tie %options, 'Tie::IxHash',
- 'datasrc' => { label=>'DBI data source ' },
- 'username' => { label=>'Database username' },
- 'password' => { label=>'Database password' },
- 'ignore_accounting' => {
- type => 'checkbox',
- label => 'Ignore accounting records from this database'
- },
- 'hide_ip' => {
- type => 'checkbox',
- label => 'Hide IP address information on session reports',
- },
- 'hide_data' => {
- type => 'checkbox',
- label => 'Hide download/upload information on session reports',
- },
- 'show_called_station' => {
- type => 'checkbox',
- label => 'Show the Called-Station-ID on session reports',
- },
- 'overlimit_groups' => { label => 'Radius groups to assign to svc_acct which has exceeded its bandwidth or time limit', } ,
- 'groups_susp_reason' => { label =>
- 'Radius group mapping to reason (via template user) (svcnum|username|username@domain reasonnum|reason)',
- type => 'textarea',
- },
-
-;
-
-$notes1 = <<'END';
-Real-time export of <b>radcheck</b>, <b>radreply</b> and <b>usergroup</b>
-tables to any SQL database for
-<a href="http://www.freeradius.org/">FreeRADIUS</a>
-or <a href="http://radius.innercite.com/">ICRADIUS</a>.
-END
-
-$notes2 = <<'END';
-An existing RADIUS database will be updated in realtime, but you can use
-<a href="../docs/man/bin/freeside-sqlradius-reset">freeside-sqlradius-reset</a>
-to delete the entire RADIUS database and repopulate the tables from the
-Freeside database. See the
-<a href="http://search.cpan.org/dist/DBI/DBI.pm#connect">DBI documentation</a>
-and the
-<a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">documentation for your DBD</a>
-for the exact syntax of a DBI data source.
-<ul>
- <li>Using FreeRADIUS 0.9.0 with the PostgreSQL backend, the db_postgresql.sql schema and postgresql.conf queries contain incompatible changes. This is fixed in 0.9.1. Only new installs with 0.9.0 and PostgreSQL are affected - upgrades and other database backends and versions are unaffected.
- <li>Using ICRADIUS, add a dummy "op" column to your database:
- <blockquote><code>
- ALTER&nbsp;TABLE&nbsp;radcheck&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
- ALTER&nbsp;TABLE&nbsp;radreply&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
- ALTER&nbsp;TABLE&nbsp;radgroupcheck&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
- ALTER&nbsp;TABLE&nbsp;radgroupreply&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='
- </code></blockquote>
- <li>Using Radiator, see the
- <a href="http://www.open.com.au/radiator/faq.html#38">Radiator FAQ</a>
- for configuration information.
-</ul>
-END
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS)',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => $notes1.
- 'This export does not export RADIUS realms (see also '.
- 'sqlradius_withdomain). '.
- $notes2
-);
-
-sub _groups_susp_reason_map { map { reverse( /^\s*(\S+)\s*(.*)$/ ) }
- split( "\n", shift->option('groups_susp_reason'));
-}
-
-sub rebless { shift; }
-
-sub export_username {
- my($self, $svc_acct) = (shift, shift);
- warn "export_username called on $self with arg $svc_acct" if $DEBUG > 1;
- $svc_acct->username;
-}
-
-sub _export_insert {
- my($self, $svc_acct) = (shift, shift);
-
- foreach my $table (qw(reply check)) {
- my $method = "radius_$table";
- my %attrib = $svc_acct->$method();
- next unless keys %attrib;
- my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
- $table, $self->export_username($svc_acct), %attrib );
- return $err_or_queue unless ref($err_or_queue);
- }
- my @groups = $svc_acct->radius_groups;
- if ( @groups ) {
- cluck localtime(). ": queuing usergroup_insert for ". $svc_acct->svcnum.
- " (". $self->export_username($svc_acct). " with ". join(", ", @groups)
- if $DEBUG;
- my $err_or_queue = $self->sqlradius_queue(
- $svc_acct->svcnum, 'usergroup_insert',
- $self->export_username($svc_acct), @groups );
- return $err_or_queue unless ref($err_or_queue);
- }
- '';
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $jobnum = '';
- if ( $self->export_username($old) ne $self->export_username($new) ) {
- my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'rename',
- $self->export_username($new), $self->export_username($old) );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- $jobnum = $err_or_queue->jobnum;
- }
-
- foreach my $table (qw(reply check)) {
- my $method = "radius_$table";
- my %new = $new->$method();
- my %old = $old->$method();
- if ( grep { !exists $old{$_} #new attributes
- || $new{$_} ne $old{$_} #changed
- } keys %new
- ) {
- my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert',
- $table, $self->export_username($new), %new );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- if ( $jobnum ) {
- my $error = $err_or_queue->depend_insert( $jobnum );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- my @del = grep { !exists $new{$_} } keys %old;
- if ( @del ) {
- my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'attrib_delete',
- $table, $self->export_username($new), @del );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
- if ( $jobnum ) {
- my $error = $err_or_queue->depend_insert( $jobnum );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
- }
-
- my $error;
- my (@oldgroups) = $old->radius_groups;
- my (@newgroups) = $new->radius_groups;
- $error = $self->sqlreplace_usergroups( $new->svcnum,
- $self->export_username($new),
- $jobnum ? $jobnum : '',
- \@oldgroups,
- \@newgroups,
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-sub _export_suspend {
- my( $self, $svc_acct ) = (shift, shift);
-
- my $new = $svc_acct->clone_suspended;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert',
- 'check', $self->export_username($new), $new->radius_check );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
-
- my $error;
- my (@newgroups) = $self->suspended_usergroups($svc_acct);
- $error =
- $self->sqlreplace_usergroups( $new->svcnum,
- $self->export_username($new),
- '',
- $svc_acct->usergroup,
- \@newgroups,
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-sub _export_unsuspend {
- my( $self, $svc_acct ) = (shift, shift);
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
- 'check', $self->export_username($svc_acct), $svc_acct->radius_check );
- unless ( ref($err_or_queue) ) {
- $dbh->rollback if $oldAutoCommit;
- return $err_or_queue;
- }
-
- my $error;
- my (@oldgroups) = $self->suspended_usergroups($svc_acct);
- $error = $self->sqlreplace_usergroups( $svc_acct->svcnum,
- $self->export_username($svc_acct),
- '',
- \@oldgroups,
- $svc_acct->usergroup,
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'delete',
- $self->export_username($svc_acct) );
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-sub sqlradius_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::sqlradius::sqlradius_$method",
- };
- $queue->insert(
- $self->option('datasrc'),
- $self->option('username'),
- $self->option('password'),
- @_,
- ) or $queue;
-}
-
-sub suspended_usergroups {
- my ($self, $svc_acct) = (shift, shift);
-
- return () unless $svc_acct;
-
- #false laziness with FS::part_export::shellcommands
- #subclass part_export?
-
- my $r = $svc_acct->cust_svc->cust_pkg->last_reason;
- my %reasonmap = $self->_groups_susp_reason_map;
- my $userspec = '';
- if ($r) {
- $userspec = $reasonmap{$r->reasonnum}
- if exists($reasonmap{$r->reasonnum});
- $userspec = $reasonmap{$r->reason}
- if (!$userspec && exists($reasonmap{$r->reason}));
- }
- my $suspend_user;
- if ($userspec =~ /^d+$/ ){
- $suspend_user = qsearchs( 'svc_acct', { 'svcnum' => $userspec } );
- }elsif ($userspec =~ /^\S+\@\S+$/){
- my ($username,$domain) = split(/\@/, $userspec);
- for my $user (qsearch( 'svc_acct', { 'username' => $username } )){
- $suspend_user = $user if $userspec eq $user->email;
- }
- }elsif ($userspec){
- $suspend_user = qsearchs( 'svc_acct', { 'username' => $userspec } );
- }
- #esalf
- return $suspend_user->radius_groups if $suspend_user;
- ();
-}
-
-sub sqlradius_insert { #subroutine, not method
- my $dbh = sqlradius_connect(shift, shift, shift);
- my( $table, $username, %attributes ) = @_;
-
- foreach my $attribute ( keys %attributes ) {
-
- my $s_sth = $dbh->prepare(
- "SELECT COUNT(*) FROM rad$table WHERE UserName = ? AND Attribute = ?"
- ) or die $dbh->errstr;
- $s_sth->execute( $username, $attribute ) or die $s_sth->errstr;
-
- if ( $s_sth->fetchrow_arrayref->[0] ) {
-
- my $u_sth = $dbh->prepare(
- "UPDATE rad$table SET Value = ? WHERE UserName = ? AND Attribute = ?"
- ) or die $dbh->errstr;
- $u_sth->execute($attributes{$attribute}, $username, $attribute)
- or die $u_sth->errstr;
-
- } else {
-
- my $i_sth = $dbh->prepare(
- "INSERT INTO rad$table ( UserName, Attribute, op, Value ) ".
- "VALUES ( ?, ?, ?, ? )"
- ) or die $dbh->errstr;
- $i_sth->execute(
- $username,
- $attribute,
- ( $attribute =~ /Password/i ? '==' : ':=' ),
- $attributes{$attribute},
- ) or die $i_sth->errstr;
-
- }
-
- }
- $dbh->disconnect;
-}
-
-sub sqlradius_usergroup_insert { #subroutine, not method
- my $dbh = sqlradius_connect(shift, shift, shift);
- my( $username, @groups ) = @_;
-
- my $s_sth = $dbh->prepare(
- "SELECT COUNT(*) FROM usergroup WHERE UserName = ? AND GroupName = ?"
- ) or die $dbh->errstr;
-
- my $sth = $dbh->prepare(
- "INSERT INTO usergroup ( UserName, GroupName ) VALUES ( ?, ? )"
- ) or die $dbh->errstr;
-
- foreach my $group ( @groups ) {
- $s_sth->execute( $username, $group ) or die $s_sth->errstr;
- if ($s_sth->fetchrow_arrayref->[0]) {
- warn localtime() . ": sqlradius_usergroup_insert attempted to reinsert " .
- "$group for $username\n"
- if $DEBUG;
- next;
- }
- $sth->execute( $username, $group )
- or die "can't insert into groupname table: ". $sth->errstr;
- }
- $dbh->disconnect;
-}
-
-sub sqlradius_usergroup_delete { #subroutine, not method
- my $dbh = sqlradius_connect(shift, shift, shift);
- my( $username, @groups ) = @_;
-
- my $sth = $dbh->prepare(
- "DELETE FROM usergroup WHERE UserName = ? AND GroupName = ?"
- ) or die $dbh->errstr;
- foreach my $group ( @groups ) {
- $sth->execute( $username, $group )
- or die "can't delete from groupname table: ". $sth->errstr;
- }
- $dbh->disconnect;
-}
-
-sub sqlradius_rename { #subroutine, not method
- my $dbh = sqlradius_connect(shift, shift, shift);
- my($new_username, $old_username) = @_;
- foreach my $table (qw(radreply radcheck usergroup )) {
- my $sth = $dbh->prepare("UPDATE $table SET Username = ? WHERE UserName = ?")
- or die $dbh->errstr;
- $sth->execute($new_username, $old_username)
- or die "can't update $table: ". $sth->errstr;
- }
- $dbh->disconnect;
-}
-
-sub sqlradius_attrib_delete { #subroutine, not method
- my $dbh = sqlradius_connect(shift, shift, shift);
- my( $table, $username, @attrib ) = @_;
-
- foreach my $attribute ( @attrib ) {
- my $sth = $dbh->prepare(
- "DELETE FROM rad$table WHERE UserName = ? AND Attribute = ?" )
- or die $dbh->errstr;
- $sth->execute($username,$attribute)
- or die "can't delete from rad$table table: ". $sth->errstr;
- }
- $dbh->disconnect;
-}
-
-sub sqlradius_delete { #subroutine, not method
- my $dbh = sqlradius_connect(shift, shift, shift);
- my $username = shift;
-
- foreach my $table (qw( radcheck radreply usergroup )) {
- my $sth = $dbh->prepare( "DELETE FROM $table WHERE UserName = ?" );
- $sth->execute($username)
- or die "can't delete from $table table: ". $sth->errstr;
- }
- $dbh->disconnect;
-}
-
-sub sqlradius_connect {
- #my($datasrc, $username, $password) = @_;
- #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
- DBI->connect(@_) or die $DBI::errstr;
-}
-
-sub sqlreplace_usergroups {
- my ($self, $svcnum, $username, $jobnum, $old, $new) = @_;
-
- # (sorta) false laziness with FS::svc_acct::replace
- my @oldgroups = @$old;
- my @newgroups = @$new;
- my @delgroups = ();
- foreach my $oldgroup ( @oldgroups ) {
- if ( grep { $oldgroup eq $_ } @newgroups ) {
- @newgroups = grep { $oldgroup ne $_ } @newgroups;
- next;
- }
- push @delgroups, $oldgroup;
- }
-
- if ( @delgroups ) {
- my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_delete',
- $username, @delgroups );
- return $err_or_queue
- unless ref($err_or_queue);
- if ( $jobnum ) {
- my $error = $err_or_queue->depend_insert( $jobnum );
- return $error if $error;
- }
- }
-
- if ( @newgroups ) {
- cluck localtime(). ": queuing usergroup_insert for $svcnum ($username) ".
- "with ". join(", ", @newgroups)
- if $DEBUG;
- my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_insert',
- $username, @newgroups );
- return $err_or_queue
- unless ref($err_or_queue);
- if ( $jobnum ) {
- my $error = $err_or_queue->depend_insert( $jobnum );
- return $error if $error;
- }
- }
- '';
-}
-
-
-#--
-
-=item usage_sessions TIMESTAMP_START TIMESTAMP_END [ SVC_ACCT [ IP [ PREFIX [ SQL_SELECT ] ] ] ]
-
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-SVC_ACCT, if specified, limits the results to the specified account.
-
-IP, if specified, limits the results to the specified IP address.
-
-PREFIX, if specified, limits the results to records with a matching
-Called-Station-ID.
-
-#SQL_SELECT defaults to * if unspecified. It can be useful to set it to
-#SUM(acctsessiontime) or SUM(AcctInputOctets), etc.
-
-Returns an arrayref of hashrefs with the following fields:
-
-=over 4
-
-=item username
-
-=item framedipaddress
-
-=item acctstarttime
-
-=item acctstoptime
-
-=item acctsessiontime
-
-=item acctinputoctets
-
-=item acctoutputoctets
-
-=item calledstationid
-
-=back
-
-=cut
-
-#some false laziness w/cust_svc::seconds_since_sqlradacct
-
-sub usage_sessions {
- my( $self, $start, $end ) = splice(@_, 0, 3);
- my $svc_acct = @_ ? shift : '';
- my $ip = @_ ? shift : '';
- my $prefix = @_ ? shift : '';
- #my $select = @_ ? shift : '*';
-
- $end ||= 2147483647;
-
- return [] if $self->option('ignore_accounting');
-
- my $dbh = sqlradius_connect( map $self->option($_),
- qw( datasrc username password ) );
-
- #select a unix time conversion function based on database type
- my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
-
- my @fields = (
- qw( username realm framedipaddress
- acctsessiontime acctinputoctets acctoutputoctets
- calledstationid
- ),
- "$str2time acctstarttime ) as acctstarttime",
- "$str2time acctstoptime ) as acctstoptime",
- );
-
- my @param = ();
- my $where = '';
-
- if ( $svc_acct ) {
- my $username = $self->export_username($svc_acct);
- if ( $svc_acct =~ /^([^@]+)\@([^@]+)$/ ) {
- $where = '( UserName = ? OR ( UserName = ? AND Realm = ? ) ) AND';
- push @param, $username, $1, $2;
- } else {
- $where = 'UserName = ? AND';
- push @param, $username;
- }
- }
-
- if ( length($ip) ) {
- $where .= ' FramedIPAddress = ? AND';
- push @param, $ip;
- }
-
- if ( length($prefix) ) {
- #assume sip: for now, else things get ugly trying to match /^\w+:$prefix/
- $where .= " CalledStationID LIKE 'sip:$prefix\%' AND";
- }
-
- push @param, $start, $end;
-
- my $sth = $dbh->prepare('SELECT '. join(', ', @fields).
- " FROM radacct
- WHERE $where
- $str2time AcctStopTime ) >= ?
- AND $str2time AcctStopTime ) <= ?
- ORDER BY AcctStartTime DESC
- ") or die $dbh->errstr;
- $sth->execute(@param) or die $sth->errstr;
-
- [ map { { %$_ } } @{ $sth->fetchall_arrayref({}) } ];
-
-}
-
-=item update_svc_acct
-
-=cut
-
-sub update_svc_acct {
- my $self = shift;
-
- my $conf = new FS::Conf;
-
- my $fdbh = dbh;
- my $dbh = sqlradius_connect( map $self->option($_),
- qw( datasrc username password ) );
-
- my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
- my @fields = qw( radacctid username realm acctsessiontime );
-
- my @param = ();
- my $where = '';
-
- my $sth = $dbh->prepare("
- SELECT RadAcctId, UserName, Realm, AcctSessionTime,
- $str2time AcctStartTime), $str2time AcctStopTime),
- AcctInputOctets, AcctOutputOctets
- FROM radacct
- WHERE FreesideStatus IS NULL
- AND AcctStopTime != 0
- ") or die $dbh->errstr;
- $sth->execute() or die $sth->errstr;
-
- while ( my $row = $sth->fetchrow_arrayref ) {
- my($RadAcctId, $UserName, $Realm, $AcctSessionTime, $AcctStartTime,
- $AcctStopTime, $AcctInputOctets, $AcctOutputOctets) = @$row;
- warn "processing record: ".
- "$RadAcctId ($UserName\@$Realm for ${AcctSessionTime}s"
- if $DEBUG;
-
- $UserName = lc($UserName) unless $conf->exists('username-uppercase');
-
- my %search = ( 'username' => $UserName );
-
- my $extra_sql = '';
- if ( ref($self) =~ /withdomain/ ) { #well...
- $extra_sql = " AND '$Realm' = ( SELECT domain FROM svc_domain
- WHERE svc_domain.svcnum = svc_acct.domsvc ) ";
- }
-
- my $oldAutoCommit = $FS::UID::AutoCommit; # can't undo side effects, but at
- local $FS::UID::AutoCommit = 0; # least we can avoid over counting
-
- my @svc_acct =
- grep { qsearch( 'export_svc', { 'exportnum' => $self->exportnum,
- 'svcpart' => $_->cust_svc->svcpart, } )
- }
- qsearch( 'svc_acct',
- { 'username' => $UserName },
- '',
- $extra_sql
- );
-
- my $errinfo = "for RADIUS detail RadAcctID $RadAcctId ".
- "(UserName $UserName, Realm $Realm)";
- my $status = 'skipped';
- if ( !@svc_acct ) {
- warn "WARNING: no svc_acct record found $errinfo - skipping\n";
- } elsif ( scalar(@svc_acct) > 1 ) {
- warn "WARNING: multiple svc_acct records found $errinfo - skipping\n";
- } else {
- warn "found svc_acct ". $svc_acct[0]->svcnum. " $errinfo\n" if $DEBUG;
- $svc_acct[0]->last_login($AcctStartTime);
- $svc_acct[0]->last_logout($AcctStopTime);
- my @stati;
- push @stati, _try_decrement($svc_acct[0], 'seconds', $AcctSessionTime);
- push @stati, _try_decrement($svc_acct[0], 'upbytes', $AcctInputOctets);
- push @stati, _try_decrement($svc_acct[0], 'downbytes', $AcctOutputOctets);
- push @stati, _try_decrement($svc_acct[0], 'totalbytes', $AcctInputOctets +
- $AcctOutputOctets);
- $status=join(' ', @stati);
- }
-
- warn "setting FreesideStatus to $status $errinfo\n" if $DEBUG;
- my $psth = $dbh->prepare("UPDATE radacct
- SET FreesideStatus = ?
- WHERE RadAcctId = ?"
- ) or die $dbh->errstr;
- $psth->execute($status, $RadAcctId) or die $psth->errstr;
-
- $fdbh->commit or die $fdbh->errstr if $oldAutoCommit;
-
- }
-
-}
-
-sub _try_decrement {
- my ($svc_acct, $column, $amount) = @_;
- if ( $svc_acct->$column !~ /^$/ ) {
- warn " svc_acct.$column found (". $svc_acct->$column.
- ") - decrementing\n"
- if $DEBUG;
- my $method = 'decrement_' . $column;
- my $error = $svc_acct->$method($amount);
- die $error if $error;
- return 'done';
- } else {
- warn " no existing $column value for svc_acct - skipping\n" if $DEBUG;
- }
- return 'skipped';
-}
-
-1;
-
diff --git a/FS/FS/part_export/sqlradius_withdomain.pm b/FS/FS/part_export/sqlradius_withdomain.pm
deleted file mode 100644
index e5a7151..0000000
--- a/FS/FS/part_export/sqlradius_withdomain.pm
+++ /dev/null
@@ -1,28 +0,0 @@
-package FS::part_export::sqlradius_withdomain;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export::sqlradius;
-
-tie my %options, 'Tie::IxHash', %FS::part_export::sqlradius::options;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS) with realms',
- 'options' => \%options,
- 'nodomain' => '',
- 'notes' => $FS::part_export::sqlradius::notes1.
- 'This export exports domains to RADIUS realms (see also '.
- 'sqlradius). '.
- $FS::part_export::sqlradius::notes2
-);
-
-@ISA = qw(FS::part_export::sqlradius);
-
-sub export_username {
- my($self, $svc_acct) = (shift, shift);
- $svc_acct->email;
-}
-
-1;
-
diff --git a/FS/FS/part_export/sysvshell.pm b/FS/FS/part_export/sysvshell.pm
deleted file mode 100644
index 244c3bf..0000000
--- a/FS/FS/part_export/sysvshell.pm
+++ /dev/null
@@ -1,25 +0,0 @@
-package FS::part_export::sysvshell;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export::passwdfile;
-
-@ISA = qw(FS::part_export::passwdfile);
-
-tie my %options, 'Tie::IxHash', %FS::part_export::passwdfile::options;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' =>
- 'Batch export of /etc/passwd and /etc/shadow files (Linux, Solaris)',
- 'options' => \%options,
- 'nodomain' => 'Y',
- 'notes' => <<'END'
-MD5 crypt requires installation of
-<a href="http://search.cpan.org/dist/Crypt-PasswdMD5">Crypt::PasswdMD5</a>
-from CPAN. Run bin/sysvshell.export to export the files.
-END
-);
-
-1;
-
diff --git a/FS/FS/part_export/textradius.pm b/FS/FS/part_export/textradius.pm
deleted file mode 100644
index 3cd7039..0000000
--- a/FS/FS/part_export/textradius.pm
+++ /dev/null
@@ -1,191 +0,0 @@
-package FS::part_export::textradius;
-
-use vars qw(@ISA %info $prefix);
-use Fcntl qw(:flock);
-use Tie::IxHash;
-use FS::UID qw(datasrc);
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote username', default=>'root' },
- 'users' => { label=>'users file location', default=>'/etc/raddb/users' },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' =>
- 'Real-time export to a text /etc/raddb/users file (Livingston, Cistron)',
- 'options' => \%options,
- 'notes' => <<'END'
-This will edit a text RADIUS users file in place on a remote server.
-Requires installation of
-<a href="http://search.cpan.org/dist/RADIUS-UserFile">RADIUS::UserFile</a>
-from CPAN. If using RADIUS::UserFile 1.01, make sure to apply
-<a href="http://rt.cpan.org/NoAuth/Bug.html?id=1210">this patch</a>. Also
-make sure <a href="http://rsync.samba.org/">rsync</a> is installed on the
-remote machine, and <a href="../docs/ssh.html">SSH is setup for unattended
-operation</a>.
-END
-);
-
-$prefix = "%%%FREESIDE_CONF%%%/export.";
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self, $svc_acct) = (shift, shift);
- $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'insert',
- $svc_acct->username, $svc_acct->radius_check, '-', $svc_acct->radius_reply);
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
- return "can't (yet?) change username with textradius"
- if $old->username ne $new->username;
- #return '' unless $old->_password ne $new->_password;
- $err_or_queue = $self->textradius_queue( $new->svcnum, 'insert',
- $new->username, $new->radius_check, '-', $new->radius_reply);
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'delete',
- $svc_acct->username );
- ref($err_or_queue) ? '' : $err_or_queue;
-}
-
-#a good idea to queue anything that could fail or take any time
-sub textradius_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::textradius::textradius_$method",
- };
- $queue->insert(
- $self->option('user')||'root',
- $self->machine,
- $self->option('users'),
- @_,
- ) or $queue;
-}
-
-sub textradius_insert { #subroutine, not method
- my( $user, $host, $users, $username, @attributes ) = @_;
-
- #silly arg processing
- my($att, @check);
- push @check, $att while @attributes && ($att=shift @attributes) ne '-';
- my %check = @check;
- my %reply = @attributes;
-
- my $file = textradius_download($user, $host, $users);
-
- eval "use RADIUS::UserFile;";
- die $@ if $@;
-
- my $userfile = new RADIUS::UserFile(
- File => $file,
- Who => [ $username ],
- Check_Items => [ keys %check ],
- ) or die "error parsing $file";
-
- $userfile->remove($username);
- $userfile->add(
- Who => $username,
- Attributes => { %check, %reply },
- Comment => 'user added by Freeside',
- ) or die "error adding to $file";
-
- $userfile->update( Who => [ $username ] )
- or die "error updating $file";
-
- textradius_upload($user, $host, $users);
-
-}
-
-sub textradius_delete { #subroutine, not method
- my( $user, $host, $users, $username ) = @_;
-
- my $file = textradius_download($user, $host, $users);
-
- eval "use RADIUS::UserFile;";
- die $@ if $@;
-
- my $userfile = new RADIUS::UserFile(
- File => $file,
- Who => [ $username ],
- ) or die "error parsing $file";
-
- $userfile->remove($username);
-
- $userfile->update( Who => [ $username ] )
- or die "error updating $file";
-
- textradius_upload($user, $host, $users);
-}
-
-sub textradius_download {
- my( $user, $host, $users ) = @_;
-
- my $dir = $prefix. datasrc;
- mkdir $dir, 0700 or die $! unless -d $dir;
- $dir .= "/$host";
- mkdir $dir, 0700 or die $! unless -d $dir;
-
- my $dest = "$dir/users";
-
- eval "use File::Rsync;";
- die $@ if $@;
- my $rsync = File::Rsync->new({ rsh => 'ssh' });
-
- open(LOCK, "+>>$dest.lock")
- and flock(LOCK,LOCK_EX)
- or die "can't open $dest.lock: $!";
-
- $rsync->exec( {
- src => "$user\@$host:$users",
- dest => $dest,
- } ); # true/false return value from exec is not working, alas
- if ( $rsync->err ) {
- die "error downloading $user\@$host:$users : ".
- 'exit status: '. $rsync->status. ', '.
- 'STDERR: '. join(" / ", $rsync->err). ', '.
- 'STDOUT: '. join(" / ", $rsync->out);
- }
-
- $dest;
-}
-
-sub textradius_upload {
- my( $user, $host, $users ) = @_;
-
- my $dir = $prefix. datasrc. "/$host";
-
- eval "use File::Rsync;";
- die $@ if $@;
- my $rsync = File::Rsync->new({
- rsh => 'ssh',
- #dry_run => 1,
- });
- $rsync->exec( {
- src => "$dir/users",
- dest => "$user\@$host:$users",
- } ); # true/false return value from exec is not working, alas
- if ( $rsync->err ) {
- die "error uploading to $user\@$host:$users : ".
- 'exit status: '. $rsync->status. ', '.
- 'STDERR: '. join(" / ", $rsync->err). ', '.
- 'STDOUT: '. join(" / ", $rsync->out);
- }
-
- flock(LOCK,LOCK_UN);
- close LOCK;
-
-}
-
-1;
-
diff --git a/FS/FS/part_export/trango.pm b/FS/FS/part_export/trango.pm
deleted file mode 100644
index e7f1126..0000000
--- a/FS/FS/part_export/trango.pm
+++ /dev/null
@@ -1,434 +0,0 @@
-package FS::part_export::trango;
-
-=head1 FS::part_export::trango
-
-This export sends SNMP SETs to a router using the Net::SNMP package. It requires the following custom fields to be defined on a router. If any of the required custom fields are not present, then the export will exit quietly.
-
-=head1 Required custom fields
-
-=over 4
-
-=item trango_address - IP address (or hostname) of the Trango AP.
-
-=item trango_comm - R/W SNMP community of the Trango AP.
-
-=item trango_ap_type - Trango AP Model. Currently 'access5830' is the only supported option.
-
-=back
-
-=head1 Optional custom fields
-
-=over 4
-
-=item trango_baseid - Base ID of the Trango AP. See L</"Generating SU IDs">.
-
-=item trango_apid - AP ID of the Trango AP. See L</"Generating SU IDs">.
-
-=back
-
-=head1 Generating SU IDs
-
-This export will/must generate a unique SU ID for each service exported to a Trango AP. It can be done such that SU IDs are globally unique, unique per Base ID, or unique per Base ID/AP ID pair. This is accomplished by setting neither trango_baseid and trango_apid, only trango_baseid, or both trango_baseid and trango_apid, respectively. An SU ID will be generated if the FS::svc_broadband virtual field specified by suid_field export option is unset, otherwise the existing value will be used.
-
-=head1 Device Support
-
-This export has been tested with the Trango Access5830 AP.
-
-
-=cut
-
-
-use strict;
-use vars qw(@ISA %info $me $DEBUG $trango_mib $counter_dir);
-
-use FS::UID qw(dbh datasrc);
-use FS::Record qw(qsearch qsearchs);
-use FS::part_export::snmp;
-
-use Tie::IxHash;
-use File::CounterFile;
-use Data::Dumper qw(Dumper);
-
-@ISA = qw(FS::part_export::snmp);
-
-tie my %options, 'Tie::IxHash', (
- 'suid_field' => {
- 'label' => 'Trango SU ID field',
- 'default' => 'trango_suid',
- 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU ID.',
- },
- 'mac_field' => {
- 'label' => 'Trango MAC address field',
- 'default' => '',
- 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU\'s MAC address.',
- },
-);
-
-%info = (
- 'svc' => 'svc_broadband',
- 'desc' => 'Sends SNMP SETs to a Trango AP.',
- 'options' => \%options,
- 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::trango for required virtual fields and usage information.',
-);
-
-$me= '[' . __PACKAGE__ . ']';
-$DEBUG = 1;
-
-$trango_mib = {
- 'access5830' => {
- 'snmpversion' => 'snmpv1',
- 'varbinds' => {
- 'insert' => [
- { # sudbDeleteOrAddID
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
- 'type' => 'INTEGER',
- 'value' => \&_trango_access5830_sudbDeleteOrAddId,
- },
- { # sudbAddMac
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
- 'type' => 'HEX_STRING',
- 'value' => \&_trango_access5830_sudbAddMac,
- },
- { # sudbAddSU
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
- 'type' => 'INTEGER',
- 'value' => 1,
- },
- ],
- 'delete' => [
- { # sudbDeleteOrAddID
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
- 'type' => 'INTEGER',
- 'value' => \&_trango_access5830_sudbDeleteOrAddId,
- },
- { # sudbDeleteSU
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
- 'type' => 'INTEGER',
- 'value' => 1,
- },
- ],
- 'replace' => [
- { # sudbDeleteOrAddID
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
- 'type' => 'INTEGER',
- 'value' => \&_trango_access5830_sudbDeleteOrAddId,
- },
- { # sudbDeleteSU
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
- 'type' => 'INTEGER',
- 'value' => 1,
- },
- { # sudbDeleteOrAddID
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
- 'type' => 'INTEGER',
- 'value' => \&_trango_access5830_sudbDeleteOrAddId,
- },
- { # sudbAddMac
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
- 'type' => 'HEX_STRING',
- 'value' => \&_trango_access5830_sudbAddMac,
- },
- { # sudbAddSU
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
- 'type' => 'INTEGER',
- 'value' => 1,
- },
- ],
- 'suspend' => [
- { # sudbDeleteOrAddID
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
- 'type' => 'INTEGER',
- 'value' => \&_trango_access5830_sudbDeleteOrAddId,
- },
- { # sudbDeleteSU
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
- 'type' => 'INTEGER',
- 'value' => 1,
- },
- ],
- 'unsuspend' => [
- { # sudbDeleteOrAddID
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
- 'type' => 'INTEGER',
- 'value' => \&_trango_access5830_sudbDeleteOrAddId,
- },
- { # sudbAddMac
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
- 'type' => 'HEX_STRING',
- 'value' => \&_trango_access5830_sudbAddMac,
- },
- { # sudbAddSU
- 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
- 'type' => 'INTEGER',
- 'value' => 1,
- },
- ],
- },
- },
-};
-
-
-sub _field_prefix { 'trango'; }
-
-sub _req_router_fields {
- map {
- $_[0]->_field_prefix . '_' . $_
- } (qw(address comm ap_type suid_field));
-}
-
-sub _get_cmd_sub {
-
- return('FS::part_export::snmp::snmp_cmd');
-
-}
-
-sub _prepare_args {
-
- my ($self, $action, $router) = (shift, shift, shift);
- my ($svc_broadband) = shift;
- my $old = shift if $action eq 'replace';
- my $field_prefix = $self->_field_prefix;
- my $error;
-
- my $ap_type = $router->getfield($field_prefix . '_ap_type');
-
- unless (exists $trango_mib->{$ap_type}) {
- return "Unsupported Trango AP type '$ap_type'";
- }
-
- $error = $self->_check_suid(
- $action, $router, $svc_broadband, ($old) ? $old : ()
- );
- return $error if $error;
-
- $error = $self->_check_mac(
- $action, $router, $svc_broadband, ($old) ? $old : ()
- );
- return $error if $error;
-
- my $ap_mib = $trango_mib->{$ap_type};
-
- my $args = [
- '-hostname' => $router->getfield($field_prefix.'_address'),
- '-version' => $ap_mib->{'snmpversion'},
- '-community' => $router->getfield($field_prefix.'_comm'),
- ];
-
- my @varbindlist = ();
-
- foreach my $oid (@{$ap_mib->{'varbinds'}->{$action}}) {
- warn "[debug]$me Processing OID '" . $oid->{'oid'} . "'" if $DEBUG;
- my $value;
- if (ref($oid->{'value'}) eq 'CODE') {
- eval {
- $value = &{$oid->{'value'}}(
- $self, $action, $router, $svc_broadband,
- (($old) ? $old : ()),
- );
- };
- return "While processing OID '" . $oid->{'oid'} . "':" . $@
- if $@;
- } else {
- $value = $oid->{'value'};
- }
-
- warn "[debug]$me Value for OID '" . $oid->{'oid'} . "': " if $DEBUG;
-
- if (defined $value) { # Skip OIDs with undefined values.
- push @varbindlist, ($oid->{'oid'}, $oid->{'type'}, $value);
- }
- }
-
-
- push @$args, ('-varbindlist', @varbindlist);
-
- return('', $args);
-
-}
-
-sub _check_suid {
-
- my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
- my $old = shift if $action eq 'replace';
- my $error;
-
- my $suid_field = $self->option('suid_field');
- unless (grep {$_ eq $suid_field} $svc_broadband->fields) {
- return "Missing Trango SU ID field. "
- . "See the trango export options for more info.";
- }
-
- my $suid = $svc_broadband->getfield($suid_field);
- if ($action eq 'replace') {
- my $old_suid = $old->getfield($suid_field);
-
- if ($old_suid ne '' and $old_suid ne $suid) {
- return 'Cannot change Trango SU ID';
- }
- }
-
- if (not $suid =~ /^\d+$/ and $action ne 'delete') {
- my $new_suid = eval { $self->_get_next_suid($router); };
- return "Error while getting next Trango SU ID: $@" if ($@);
-
- warn "[debug]$me Got new SU ID: $new_suid" if $DEBUG;
- $svc_broadband->set($suid_field, $new_suid);
-
- #FIXME: Probably a bad hack.
- # We need to update the SU ID field in the database.
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::svc_Common::noexport_hack = 1;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $svcnum = $svc_broadband->svcnum;
-
- my $old_svc = qsearchs('svc_broadband', { svcnum => $svcnum });
- unless ($old_svc) {
- return "Unable to retrieve svc_broadband with svcnum '$svcnum";
- }
-
- my $svcpart = $svc_broadband->svcpart
- ? $svc_broadband->svcpart
- : $svc_broadband->cust_svc->svcpart;
-
- my $new_svc = new FS::svc_broadband {
- $old_svc->hash,
- $suid_field => $new_suid,
- svcpart => $svcpart,
- };
-
- $error = $new_svc->check;
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return "Error while updating the Trango SU ID: $error" if $error;
- }
-
- warn "[debug]$me Updating svc_broadband with SU ID '$new_suid'...\n" .
- &Dumper($new_svc) if $DEBUG;
-
- $error = eval { $new_svc->replace($old_svc); };
-
- if ($@ or $error) {
- $error ||= $@;
- $dbh->rollback if $oldAutoCommit;
- return "Error while updating the Trango SU ID: $error" if $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- }
-
- return '';
-
-}
-
-sub _check_mac {
-
- my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
- my $old = shift if $action eq 'replace';
-
- my $mac_field = $self->option('mac_field');
- unless (grep {$_ eq $mac_field} $svc_broadband->fields) {
- return "Missing Trango MAC address field. "
- . "See the trango export options for more info.";
- }
-
- my $mac_addr = $svc_broadband->getfield($mac_field);
- unless (length(join('', $mac_addr =~ /[0-9a-fA-F]/g)) == 12) {
- return "Invalid Trango MAC address: $mac_addr";
- }
-
- return('');
-
-}
-
-sub _get_next_suid {
-
- my ($self, $router) = (shift, shift);
-
- my $counter_dir = '/usr/local/etc/freeside/export.'. datasrc . '/trango';
- my $baseid = $router->getfield('trango_baseid');
- my $apid = $router->getfield('trango_apid');
-
- my $counter_file_suffix = '';
- if ($baseid ne '') {
- $counter_file_suffix .= "_B$baseid";
- if ($apid ne '') {
- $counter_file_suffix .= "_A$apid";
- }
- }
-
- my $counter_file = $counter_dir . '/SUID' . $counter_file_suffix;
-
- warn "[debug]$me Using SUID counter file '$counter_file'";
-
- my $suid = eval {
- mkdir $counter_dir, 0700 unless -d $counter_dir;
-
- my $cf = new File::CounterFile($counter_file, 0);
- $cf->inc;
- };
-
- die "Error generating next Trango SU ID: $@" if (not $suid or $@);
-
- return($suid);
-
-}
-
-
-
-# Trango-specific subroutines for generating varbind values.
-#
-# All subs should die on error, and return undef to decline. OIDs that
-# decline will not be added to varbinds.
-
-sub _trango_access5830_sudbDeleteOrAddId {
-
- my ($self, $action, $router) = (shift, shift, shift);
- my ($svc_broadband) = shift;
- my $old = shift if $action eq 'replace';
-
- my $suid = $svc_broadband->getfield($self->option('suid_field'));
-
- # Sanity check.
- unless ($suid =~ /^\d+$/) {
- if ($action eq 'delete') {
- # Silently ignore. If we don't have a valid SU ID now, we probably
- # never did.
- return undef;
- } else {
- die "Invalid Trango SU ID '$suid'";
- }
- }
-
- return ($suid);
-
-}
-
-sub _trango_access5830_sudbAddMac {
-
- my ($self, $action, $router) = (shift, shift, shift);
- my ($svc_broadband) = shift;
- my $old = shift if $action eq 'replace';
-
- my $mac_addr = $svc_broadband->getfield($self->option('mac_field'));
- $mac_addr = join('', $mac_addr =~ /[0-9a-fA-F]/g);
-
- # Sanity check.
- die "Invalid Trango MAC address '$mac_addr'" unless (length($mac_addr)==12);
-
- return($mac_addr);
-
-}
-
-
-=head1 BUGS
-
-Plenty, I'm sure.
-
-=cut
-
-
-1;
diff --git a/FS/FS/part_export/vpopmail.pm b/FS/FS/part_export/vpopmail.pm
deleted file mode 100644
index 4cda657..0000000
--- a/FS/FS/part_export/vpopmail.pm
+++ /dev/null
@@ -1,254 +0,0 @@
-package FS::part_export::vpopmail;
-
-use vars qw(@ISA %info @saltset $exportdir);
-use Fcntl qw(:flock);
-use Tie::IxHash;
-use File::Path;
-use FS::UID qw( datasrc );
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- #'machine' => { label=>'vpopmail machine', },
- 'dir' => { label=>'directory', }, # ?more info? default?
- 'uid' => { label=>'vpopmail uid' },
- 'gid' => { label=>'vpopmail gid' },
- 'restart' => { label=> 'vpopmail restart command',
- default=> 'cd /home/vpopmail/domains; for domain in *; do /home/vpopmail/bin/vmkpasswd $domain; done; /var/qmail/bin/qmail-newu; killall -HUP qmail-send',
- },
-;
-
-%info = (
- 'svc' => 'svc_acct',
- 'desc' => 'Real-time export to vpopmail text files',
- 'options' => \%options,
- 'notes' => <<'END'
-This export is currently unmaintained. See shellcommands_withdomain for an
-export that uses vpopmail CLI commands instead.<BR>
-<BR>
-Real time export to <a href="http://inter7.com/vpopmail/">vpopmail</a> text
-files. <a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
-must be installed, and you will need to
-<a href="../docs/ssh.html">setup SSH for unattended operation</a>
-to <b>vpopmail</b>@<i>export.host</i>.
-END
-);
-
-@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self, $svc_acct) = (shift, shift);
- $self->vpopmail_queue( $svc_acct->svcnum, 'insert',
- $svc_acct->username,
- crypt($svc_acct->_password,$saltset[int(rand(64))].$saltset[int(rand(64))]),
- $svc_acct->domain,
- $svc_acct->quota,
- $svc_acct->finger,
- );
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
-
- my $cpassword = crypt(
- $new->_password, $saltset[int(rand(64))].$saltset[int(rand(64))]
- );
-
- return "can't change username with vpopmail"
- if $old->username ne $new->username;
-
- #no.... if mail can't be preserved, better to disallow username changes
- #if ($old->username ne $new->username || $old->domain ne $new->domain ) {
- # vpopmail_queue( $svc_acct->svcnum, 'delete',
- # $old->username, $old->domain
- # );
- # vpopmail_queue( $svc_acct->svcnum, 'insert',
- # $new->username,
- # $cpassword,
- # $new->domain,
- # );
-
- return '' unless $old->_password ne $new->_password;
-
- $self->vpopmail_queue( $new->svcnum, 'replace',
- $new->username, $cpassword, $new->domain, $new->quota, $new->finger );
-}
-
-sub _export_delete {
- my( $self, $svc_acct ) = (shift, shift);
- $self->vpopmail_queue( $svc_acct->svcnum, 'delete',
- $svc_acct->username, $svc_acct->domain );
-}
-
-#a good idea to queue anything that could fail or take any time
-sub vpopmail_queue {
- my( $self, $svcnum, $method ) = (shift, shift, shift);
-
- my $exportdir = "%%%FREESIDE_EXPORT%%%/export." . datasrc;
- mkdir $exportdir, 0700 or die $! unless -d $exportdir;
- $exportdir .= "/vpopmail";
- mkdir $exportdir, 0700 or die $! unless -d $exportdir;
- $exportdir .= '/'. $self->machine;
- mkdir $exportdir, 0700 or die $! unless -d $exportdir;
- mkdir "$exportdir/domains", 0700 or die $! unless -d "$exportdir/domains";
-
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::vpopmail::vpopmail_$method",
- };
- $queue->insert(
- $exportdir,
- $self->machine,
- $self->option('dir'),
- $self->option('uid'),
- $self->option('gid'),
- $self->option('restart'),
- @_
- );
-}
-
-sub vpopmail_insert { #subroutine, not method
- my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
- my( $username, $password, $domain, $quota, $finger ) = @_;
-
- mkdir "$exportdir/domains/$domain", 0700 or die $!
- unless -d "$exportdir/domains/$domain";
-
- (open(VPASSWD, ">>$exportdir/domains/$domain/vpasswd")
- and flock(VPASSWD,LOCK_EX)
- ) or die "can't open vpasswd file for $username\@$domain: ".
- "$exportdir/domains/$domain/vpasswd: $!";
- print VPASSWD join(":",
- $username,
- $password,
- '1',
- '0',
- $finger,
- "$dir/domains/$domain/$username",
- $quota ? $quota.'S' : 'NOQUOTA',
- ), "\n";
-
- flock(VPASSWD,LOCK_UN);
- close(VPASSWD);
-
- for my $mkdir (
- grep { ! -d $_ } map { "$exportdir/domains/$domain/$username$_" }
- ( '', qw( /Maildir /Maildir/cur /Maildir/new /Maildir/tmp ) )
- ) {
- mkdir $mkdir, 0700 or die "can't mkdir $mkdir: $!";
- }
-
- vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
-
-}
-
-sub vpopmail_replace { #subroutine, not method
- my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
- my( $username, $password, $domain, $quota, $finger ) = @_;
-
- (open(VPASSWD, "$exportdir/domains/$domain/vpasswd")
- and flock(VPASSWD,LOCK_EX)
- ) or die "can't open $exportdir/domains/$domain/vpasswd: $!";
-
- open(VPASSWDTMP, ">$exportdir/domains/$domain/vpasswd.tmp")
- or die "Can't open $exportdir/domains/$domain/vpasswd.tmp: $!";
-
- while (<VPASSWD>) {
- my ($mailbox, $pw, $vuid, $vgid, $vfinger, $vdir, $vquota, @rest) =
- split(':', $_);
- if ( $username ne $mailbox ) {
- print VPASSWDTMP $_;
- next
- }
- print VPASSWDTMP join (':',
- $mailbox,
- $password,
- '1',
- '0',
- $finger,
- "$dir/domains/$domain/$username", #$vdir
- $quota ? $quota.'S' : 'NOQUOTA',
- ), "\n";
- }
-
- close(VPASSWDTMP);
-
- rename "$exportdir/domains/$domain/vpasswd.tmp", "$exportdir/domains/$domain/vpasswd"
- or die "Can't rename $exportdir/domains/$domain/vpasswd.tmp: $!";
-
- flock(VPASSWD,LOCK_UN);
- close(VPASSWD);
-
- vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
-
-}
-
-sub vpopmail_delete { #subroutine, not method
- my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
- my( $username, $domain ) = @_;
-
- (open(VPASSWD, "$exportdir/domains/$domain/vpasswd")
- and flock(VPASSWD,LOCK_EX)
- ) or die "can't open $exportdir/domains/$domain/vpasswd: $!";
-
- open(VPASSWDTMP, ">$exportdir/domains/$domain/vpasswd.tmp")
- or die "Can't open $exportdir/domains/$domain/vpasswd.tmp: $!";
-
- while (<VPASSWD>) {
- my ($mailbox, $rest) = split(':', $_);
- print VPASSWDTMP $_ unless $username eq $mailbox;
- }
-
- close(VPASSWDTMP);
-
- rename "$exportdir/domains/$domain/vpasswd.tmp",
- "$exportdir/domains/$domain/vpasswd"
- or die "Can't rename $exportdir/domains/$domain/vpasswd.tmp: $!";
-
- flock(VPASSWD,LOCK_UN);
- close(VPASSWD);
-
- rmtree "$exportdir/domains/$domain/$username"
- or die "can't rmtree $exportdir/domains/$domain/$username: $!";
-
- vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
-}
-
-sub vpopmail_sync {
- my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
-
- chdir $exportdir;
-# my @args = ( $rsync, "-rlpt", "-e", $ssh, "domains/",
-# "vpopmail\@$machine:$dir/domains/" );
-# system {$args[0]} @args;
-
- eval "use File::Rsync;";
- die $@ if $@;
-
- my $rsync = File::Rsync->new({ rsh => 'ssh' });
-
- $rsync->exec( {
- recursive => 1,
- perms => 1,
- times => 1,
- src => "$exportdir/domains/",
- dest => "vpopmail\@$machine:$dir/domains/",
- } ); # true/false return value from exec is not working, alas
- if ( $rsync->err ) {
- die "error uploading to vpopmail\@$machine:$dir/domains/ : ".
- 'exit status: '. $rsync->status. ', '.
- 'STDERR: '. join(" / ", $rsync->err). ', '.
- 'STDOUT: '. join(" / ", $rsync->out);
- }
-
- eval "use Net::SSH qw(ssh);";
- die $@ if $@;
-
- ssh("vpopmail\@$machine", $restart) if $restart;
-}
-
-1;
-
diff --git a/FS/FS/part_export/www_plesk.pm b/FS/FS/part_export/www_plesk.pm
deleted file mode 100644
index 82d5557..0000000
--- a/FS/FS/part_export/www_plesk.pm
+++ /dev/null
@@ -1,138 +0,0 @@
-package FS::part_export::www_plesk;
-
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'URL' => { label=>'URL' },
- 'login' => { label=>'Login' },
- 'password' => { label=>'Password' },
- 'template' => { label=>'Domain Template' },
- 'web' => { label=>'Host Website',
- type=>'checkbox' },
- 'debug' => { label=>'Enable debugging',
- type=>'checkbox' },
-;
-
-%info = (
- 'svc' => 'svc_www',
- 'desc' => 'Real-time export to Plesk managed hosting service',
- 'options'=> \%options,
- 'notes' => <<'END'
-Real-time export to
-<a href="http://www.swsoft.com/">Plesk</a> managed server.
-Requires installation of
-<a href="http://search.cpan.org/dist/Net-Plesk">Net::Plesk</a>
-from CPAN.
-END
-);
-
-sub rebless { shift; }
-
-# experiment: want the status of these right away (don't want account to
-# create or whatever and then get error in the queue from dup username or
-# something), so no queueing
-
-sub _export_insert {
- my( $self, $www ) = ( shift, shift );
-
- eval "use Net::Plesk;";
- return $@ if $@;
-
- my $plesk = new Net::Plesk (
- 'POST' => $self->option('URL'),
- ':HTTP_AUTH_LOGIN' => $self->option('login'),
- ':HTTP_AUTH_PASSWD' => $self->option('password'),
- );
-
- my $gcresp = $plesk->client_get( $www->svc_acct->username );
- return $gcresp->errortext
- unless $gcresp->is_success;
-
- unless ($gcresp->id) {
- my $cust_main = $www->cust_svc->cust_pkg->cust_main;
- $gcresp = $plesk->client_add( $cust_main->name,
- $www->svc_acct->username,
- $www->svc_acct->_password,
- $cust_main->daytime,
- $cust_main->fax,
- $cust_main->invoicing_list->[0],
- $cust_main->address1 . $cust_main->address2,
- $cust_main->city,
- $cust_main->state,
- $cust_main->zip,
- $cust_main->country,
- );
- return $gcresp->errortext
- unless $gcresp->is_success;
- }
-
- $plesk->client_ippool_add_ip ( $gcresp->id,
- $www->domain_record->recdata,
- );
-
- if ($self->option('web')) {
- $self->_plesk_command( 'domain_add',
- $www->domain_record->svc_domain->domain,
- $gcresp->id,
- $www->domain_record->recdata,
- $self->option('template')?$self->option('template'):'',
- $www->svc_acct->username,
- $www->svc_acct->_password,
- );
- }else{
- $self->_plesk_command( 'domain_add',
- $www->domain_record->svc_domain->domain,
- $gcresp->id,
- $www->domain_record->recdata,
- $self->option('template')?$self->option('template'):'',
- );
- }
-}
-
-sub _plesk_command {
- my( $self, $method, @args ) = @_;
-
- eval "use Net::Plesk;";
- return $@ if $@;
-
- local($Net::Plesk::DEBUG) = 1
- if $self->option('debug');
-
- my $plesk = new Net::Plesk (
- 'POST' => $self->option('URL'),
- ':HTTP_AUTH_LOGIN' => $self->option('login'),
- ':HTTP_AUTH_PASSWD' => $self->option('password'),
- );
-
- my $response = $plesk->$method(@args);
- return $response->errortext unless $response->is_success;
- '';
-
-}
-
-sub _export_replace {
- my( $self, $new, $old ) = (shift, shift, shift);
-
- return "can't change domain with Plesk"
- if $old->domain_record->svc_domain->domain ne
- $new->domain_record->svc_domain->domain;
-
- return "can't change client with Plesk"
- if $old->svc_acct->username ne
- $new->svc_acct->username;
-
- return '';
-
-}
-
-sub _export_delete {
- my( $self, $www ) = ( shift, shift );
- $self->_plesk_command( 'domain_del', $www->domain_record->svc_domain->domain);
-}
-
-1;
-
diff --git a/FS/FS/part_export/www_shellcommands.pm b/FS/FS/part_export/www_shellcommands.pm
deleted file mode 100644
index 7e4be9c..0000000
--- a/FS/FS/part_export/www_shellcommands.pm
+++ /dev/null
@@ -1,190 +0,0 @@
-package FS::part_export::www_shellcommands;
-
-use strict;
-use vars qw(@ISA %info);
-use Tie::IxHash;
-use FS::part_export;
-
-@ISA = qw(FS::part_export);
-
-tie my %options, 'Tie::IxHash',
- 'user' => { label=>'Remote username', default=>'root' },
- 'useradd' => { label=>'Insert command',
- default=>'mkdir $homedir/$zone; chown $username $homedir/$zone; ln -s $homedir/$zone /var/www/$zone',
- },
- 'userdel' => { label=>'Delete command',
- default=>'[ -n "$zone" ] && rm -rf /var/www/$zone; rm -rf $homedir/$zone',
- },
- 'usermod' => { label=>'Modify command',
- default=>'[ -n "$old_zone" ] && rm /var/www/$old_zone; [ "$old_zone" != "$new_zone" -a -n "$new_zone" ] && ( mv $old_homedir/$old_zone $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone ); [ "$old_username" != "$new_username" ] && chown -R $new_username $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone',
- },
- 'suspend' => { label=>'Suspension command',
- default=>'[ -n "$zone" ] && chmod 0 /var/www/$zone',
- },
- 'unsuspend'=> { label=>'Unsuspension command',
- default=>'[ -n "$zone" ] && chmod 755 /var/www/$zone',
- },
-;
-
-%info = (
- 'svc' => 'svc_www',
- 'desc' => 'Run remote commands via SSH, for virtual web sites (directory maintenance, FrontPage, ISPMan)',
- 'options' => \%options,
- 'notes' => <<'END'
-Run remote commands via SSH, for virtual web sites. You will need to
-<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
-<BR><BR>Use these buttons for some useful presets:
-<UL>
- <LI>
- <INPUT TYPE="button" VALUE="Maintain directories" onClick='
- this.form.user.value = "root";
- this.form.useradd.value = "mkdir $homedir/$zone; chown $username $homedir/$zone; ln -s $homedir/$zone /var/www/$zone";
- this.form.userdel.value = "[ -n \"$zone\" ] && rm -rf /var/www/$zone; rm -rf $homedir/$zone";
- this.form.usermod.value = "[ -n \"$old_zone\" ] && rm /var/www/$old_zone; [ \"$old_zone\" != \"$new_zone\" -a -n \"$new_zone\" ] && ( mv $old_homedir/$old_zone $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone ); [ \"$old_username\" != \"$new_username\" ] && chown -R $new_username $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone";
- this.form.suspend.value = "[ -n \"$zone\" ] && chmod 0 /var/www/$zone";
- this.form.unsuspend.value = "[ -n \"$zone\" ] && chmod 755 /var/www/$zone";
- '>
- <LI>
- <INPUT TYPE="button" VALUE="FrontPage extensions" onClick='
- this.form.user.value = "root";
- this.form.useradd.value = "/usr/local/frontpage/version5.0/bin/owsadm.exe -o install -p 80 -m $zone -xu $username -xg www-data -s /etc/apache/httpd.conf -u $username -pw $_password";
- this.form.userdel.value = "/usr/local/frontpage/version5.0/bin/owsadm.exe -o uninstall -p 80 -m $zone -s /etc/apache/httpd.conf";
- this.form.usermod.value = "";
- this.form.suspend.value = "";
- this.form.unsuspend.value = "";
- '>
- <LI>
- <INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
- this.form.user.value = "root";
- this.form.useradd.value = "/usr/local/ispman/bin/ispman.addvhost -d $domain $bare_zone";
- this.form.userdel.value = "/usr/local/ispman/bin/ispman.deletevhost -d $domain $bare_zone";
- this.form.usermod.value = "";
- this.form.suspend.value = "";
- this.form.unsuspend.value = "";
- '></UL>
-The following variables are available for interpolation (prefixed with
-<code>new_</code> or <code>old_</code> for replace operations):
-<UL>
- <LI><code>$zone</code> - fully-qualified zone of this virtual host
- <LI><code>$bare_zone</code> - just the zone of this virtual host, without the domain portion
- <LI><code>$domain</code> - base domain
- <LI><code>$username</code>
- <LI><code>$_password</code>
- <LI><code>$homedir</code>
- <LI>All other fields in <a href="../docs/schema.html#svc_www">svc_www</a>
- are also available.
-</UL>
-END
-);
-
-
-sub rebless { shift; }
-
-sub _export_insert {
- my($self) = shift;
- $self->_export_command('useradd', @_);
-}
-
-sub _export_delete {
- my($self) = shift;
- $self->_export_command('userdel', @_);
-}
-
-sub _export_suspend {
- my($self) = shift;
- $self->_export_command('suspend', @_);
-}
-
-sub _export_unsuspend {
- my($self) = shift;
- $self->_export_command('unsuspend', @_);
-}
-
-sub _export_command {
- my ( $self, $action, $svc_www) = (shift, shift, shift);
- my $command = $self->option($action);
- return '' if $command =~ /^\s*$/;
-
- #set variable for the command
- no strict 'vars';
- {
- no strict 'refs';
- ${$_} = $svc_www->getfield($_) foreach $svc_www->fields;
- }
- my $domain_record = $svc_www->domain_record; # or die ?
- my $zone = $domain_record->zone; # or die ?
- my $domain = $domain_record->svc_domain->domain;
- ( my $bare_zone = $zone ) =~ s/\.$domain$//;
- my $svc_acct = $svc_www->svc_acct; # or die ?
- my $username = $svc_acct->username;
- my $_password = $svc_acct->_password;
- my $homedir = $svc_acct->dir; # or die ?
-
- #done setting variables for the command
-
- $self->shellcommands_queue( $svc_www->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => eval(qq("$command")),
- );
-}
-
-sub _export_replace {
- my($self, $new, $old ) = (shift, shift, shift);
- my $command = $self->option('usermod');
-
- #set variable for the command
- no strict 'vars';
- {
- no strict 'refs';
- ${"old_$_"} = $old->getfield($_) foreach $old->fields;
- ${"new_$_"} = $new->getfield($_) foreach $new->fields;
- }
- my $old_domain_record = $old->domain_record; # or die ?
- my $old_zone = $old_domain_record->zone; # or die ?
- my $old_domain = $old_domain_record->svc_domain->domain;
- ( my $old_bare_zone = $old_zone ) =~ s/\.$old_domain$//;
- my $old_svc_acct = $old->svc_acct; # or die ?
- my $old_username = $old_svc_acct->username;
- my $old_homedir = $old_svc_acct->dir; # or die ?
-
- my $new_domain_record = $new->domain_record; # or die ?
- my $new_zone = $new_domain_record->zone; # or die ?
- my $new_domain = $new_domain_record->svc_domain->domain;
- ( my $new_bare_zone = $new_zone ) =~ s/\.$new_domain$//;
- my $new_svc_acct = $new->svc_acct; # or die ?
- my $new_username = $new_svc_acct->username;
- #my $new__password = $new_svc_acct->_password;
- my $new_homedir = $new_svc_acct->dir; # or die ?
-
- #done setting variables for the command
-
- $self->shellcommands_queue( $new->svcnum,
- user => $self->option('user')||'root',
- host => $self->machine,
- command => eval(qq("$command")),
- );
-}
-
-#a good idea to queue anything that could fail or take any time
-sub shellcommands_queue {
- my( $self, $svcnum ) = (shift, shift);
- my $queue = new FS::queue {
- 'svcnum' => $svcnum,
- 'job' => "FS::part_export::www_shellcommands::ssh_cmd",
- };
- $queue->insert( @_ );
-}
-
-sub ssh_cmd { #subroutine, not method
- use Net::SSH '0.08';
- &Net::SSH::ssh_cmd( { @_ } );
-}
-
-#sub shellcommands_insert { #subroutine, not method
-#}
-#sub shellcommands_replace { #subroutine, not method
-#}
-#sub shellcommands_delete { #subroutine, not method
-#}
-
diff --git a/FS/FS/part_export_option.pm b/FS/FS/part_export_option.pm
deleted file mode 100644
index e759404..0000000
--- a/FS/FS/part_export_option.pm
+++ /dev/null
@@ -1,134 +0,0 @@
-package FS::part_export_option;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::part_export;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::part_export_option - Object methods for part_export_option records
-
-=head1 SYNOPSIS
-
- use FS::part_export_option;
-
- $record = new FS::part_export_option \%hash;
- $record = new FS::part_export_option { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_export_option object represents an export option.
-FS::part_export_option inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item optionnum - primary key
-
-=item exportnum - export (see L<FS::part_export>)
-
-=item optionname - option name
-
-=item optionvalue - option value
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new export option. To add the export option to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_export_option'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid export option. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('optionnum')
- || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum')
- || $self->ut_alpha('optionname')
- || $self->ut_anything('optionvalue')
- ;
- return $error if $error;
-
- return "Unknown exportnum: ". $self->exportnum
- unless qsearchs('part_export', { 'exportnum' => $self->exportnum } );
-
- #check options & values?
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-Possibly.
-
-=head1 SEE ALSO
-
-L<FS::part_export>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
deleted file mode 100644
index 84502b7..0000000
--- a/FS/FS/part_pkg.pm
+++ /dev/null
@@ -1,896 +0,0 @@
-package FS::part_pkg;
-
-use strict;
-use vars qw( @ISA %plans $DEBUG );
-use Carp qw(carp cluck confess);
-use Tie::IxHash;
-use FS::Conf;
-use FS::Record qw( qsearch qsearchs dbh dbdef );
-use FS::pkg_svc;
-use FS::part_svc;
-use FS::cust_pkg;
-use FS::agent_type;
-use FS::type_pkgs;
-use FS::part_pkg_option;
-use FS::pkg_class;
-use FS::agent;
-
-@ISA = qw( FS::m2m_Common FS::Record ); # FS::option_Common ); # this can use option_Common
- # when all the plandata bs is
- # gone
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::part_pkg - Object methods for part_pkg objects
-
-=head1 SYNOPSIS
-
- use FS::part_pkg;
-
- $record = new FS::part_pkg \%hash
- $record = new FS::part_pkg { 'column' => 'value' };
-
- $custom_record = $template_record->clone;
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- @pkg_svc = $record->pkg_svc;
-
- $svcnum = $record->svcpart;
- $svcnum = $record->svcpart( 'svc_acct' );
-
-=head1 DESCRIPTION
-
-An FS::part_pkg object represents a package definition. FS::part_pkg
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item pkgpart - primary key (assigned automatically for new package definitions)
-
-=item pkg - Text name of this package definition (customer-viewable)
-
-=item comment - Text name of this package definition (non-customer-viewable)
-
-=item classnum - Optional package class (see L<FS::pkg_class>)
-
-=item promo_code - Promotional code
-
-=item setup - Setup fee expression (deprecated)
-
-=item freq - Frequency of recurring fee
-
-=item recur - Recurring fee expression (deprecated)
-
-=item setuptax - Setup fee tax exempt flag, empty or `Y'
-
-=item recurtax - Recurring fee tax exempt flag, empty or `Y'
-
-=item taxclass - Tax class
-
-=item plan - Price plan
-
-=item plandata - Price plan data (deprecated - see L<FS::part_pkg_option> instead)
-
-=item disabled - Disabled flag, empty or `Y'
-
-=item pay_weight - Weight (relative to credit_weight and other package definitions) that controls payment application to specific line items.
-
-=item credit_weight - Weight (relative to other package definitions) that controls credit application to specific line items.
-
-=item agentnum - Optional agentnum (see L<FS::agent>)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new package definition. To add the package definition to
-the database, see L<"insert">.
-
-=cut
-
-sub table { 'part_pkg'; }
-
-=item clone
-
-An alternate constructor. Creates a new package definition by duplicating
-an existing definition. A new pkgpart is assigned and `(CUSTOM) ' is prepended
-to the comment field. To add the package definition to the database, see
-L<"insert">.
-
-=cut
-
-sub clone {
- my $self = shift;
- my $class = ref($self);
- my %hash = $self->hash;
- $hash{'pkgpart'} = '';
- $hash{'comment'} = "(CUSTOM) ". $hash{'comment'}
- unless $hash{'comment'} =~ /^\(CUSTOM\) /;
- #new FS::part_pkg ( \%hash ); # ?
- new $class ( \%hash ); # ?
-}
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this package definition to the database. If there is an error,
-returns the error, otherwise returns false.
-
-Currently available options are: I<pkg_svc>, I<primary_svc>, I<cust_pkg>,
-I<custnum_ref> and I<options>.
-
-If I<pkg_svc> is set to a hashref with svcparts as keys and quantities as
-values, appropriate FS::pkg_svc records will be inserted.
-
-If I<primary_svc> is set to the svcpart of the primary service, the appropriate
-FS::pkg_svc record will be updated.
-
-If I<cust_pkg> is set to a pkgnum of a FS::cust_pkg record (or the FS::cust_pkg
-record itself), the object will be updated to point to this package definition.
-
-In conjunction with I<cust_pkg>, if I<custnum_ref> is set to a scalar reference,
-the scalar will be updated with the custnum value from the cust_pkg record.
-
-If I<options> is set to a hashref of options, appropriate FS::part_pkg_option
-records will be inserted.
-
-=cut
-
-sub insert {
- my $self = shift;
- my %options = @_;
- warn "FS::part_pkg::insert called on $self with options ".
- join(', ', map "$_=>$options{$_}", keys %options)
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- warn " saving legacy plandata" if $DEBUG;
- my $plandata = $self->get('plandata');
- $self->set('plandata', '');
-
- warn " inserting part_pkg record" if $DEBUG;
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $plandata ) {
-
- warn " inserting part_pkg_option records for plandata" if $DEBUG;
- foreach my $part_pkg_option (
- map { /^(\w+)=(.*)$/ or do { $dbh->rollback if $oldAutoCommit;
- return "illegal plandata: $plandata";
- };
- new FS::part_pkg_option {
- 'pkgpart' => $self->pkgpart,
- 'optionname' => $1,
- 'optionvalue' => $2,
- };
- }
- split("\n", $plandata)
- ) {
- my $error = $part_pkg_option->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- } elsif ( $options{'options'} ) {
-
- warn " inserting part_pkg_option records for options hashref" if $DEBUG;
- foreach my $optionname ( keys %{$options{'options'}} ) {
-
- my $part_pkg_option =
- new FS::part_pkg_option {
- 'pkgpart' => $self->pkgpart,
- 'optionname' => $optionname,
- 'optionvalue' => $options{'options'}->{$optionname},
- };
-
- my $error = $part_pkg_option->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- }
-
- my $conf = new FS::Conf;
- if ( $conf->exists('agent_defaultpkg') ) {
- warn " agent_defaultpkg set; allowing all agents to purchase package"
- if $DEBUG;
- foreach my $agent_type ( qsearch('agent_type', {} ) ) {
- my $type_pkgs = new FS::type_pkgs({
- 'typenum' => $agent_type->typenum,
- 'pkgpart' => $self->pkgpart,
- });
- my $error = $type_pkgs->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- warn " inserting pkg_svc records" if $DEBUG;
- my $pkg_svc = $options{'pkg_svc'} || {};
- foreach my $part_svc ( qsearch('part_svc', {} ) ) {
- my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
- my $primary_svc =
- ( $options{'primary_svc'} && $options{'primary_svc'}==$part_svc->svcpart )
- ? 'Y'
- : '';
-
- my $pkg_svc = new FS::pkg_svc( {
- 'pkgpart' => $self->pkgpart,
- 'svcpart' => $part_svc->svcpart,
- 'quantity' => $quantity,
- 'primary_svc' => $primary_svc,
- } );
- my $error = $pkg_svc->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- if ( $options{'cust_pkg'} ) {
- warn " updating cust_pkg record " if $DEBUG;
- my $old_cust_pkg =
- ref($options{'cust_pkg'})
- ? $options{'cust_pkg'}
- : qsearchs('cust_pkg', { pkgnum => $options{'cust_pkg'} } );
- ${ $options{'custnum_ref'} } = $old_cust_pkg->custnum
- if $options{'custnum_ref'};
- my %hash = $old_cust_pkg->hash;
- $hash{'pkgpart'} = $self->pkgpart,
- my $new_cust_pkg = new FS::cust_pkg \%hash;
- local($FS::cust_pkg::disable_agentcheck) = 1;
- my $error = $new_cust_pkg->replace($old_cust_pkg);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error modifying cust_pkg record: $error";
- }
- }
-
- warn " commiting transaction" if $DEBUG;
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-=item delete
-
-Currently unimplemented.
-
-=cut
-
-sub delete {
- return "Can't (yet?) delete package definitions.";
-# check & make sure the pkgpart isn't in cust_pkg or type_pkgs?
-}
-
-=item replace OLD_RECORD [ , OPTION => VALUE ... ]
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-Currently available options are: I<pkg_svc> and I<primary_svc>
-
-If I<pkg_svc> is set to a hashref with svcparts as keys and quantities as
-values, the appropriate FS::pkg_svc records will be replace.
-
-If I<primary_svc> is set to the svcpart of the primary service, the appropriate
-FS::pkg_svc record will be updated.
-
-=cut
-
-sub replace {
- my( $new, $old ) = ( shift, shift );
- my %options = @_;
-
- # We absolutely have to have an old vs. new record to make this work.
- if (!defined($old)) {
- $old = qsearchs( 'part_pkg', { 'pkgpart' => $new->pkgpart } );
- }
-
- warn "FS::part_pkg::replace called on $new to replace $old ".
- "with options %options"
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- warn " saving legacy plandata" if $DEBUG;
- my $plandata = $new->get('plandata');
- $new->set('plandata', '');
-
- warn " deleting old part_pkg_option records" if $DEBUG;
- foreach my $part_pkg_option ( $old->part_pkg_option ) {
- my $error = $part_pkg_option->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- warn " replacing part_pkg record" if $DEBUG;
- my $error = $new->SUPER::replace($old);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- warn " inserting part_pkg_option records for plandata" if $DEBUG;
- foreach my $part_pkg_option (
- map { /^(\w+)=(.*)$/ or do { $dbh->rollback if $oldAutoCommit;
- return "illegal plandata: $plandata";
- };
- new FS::part_pkg_option {
- 'pkgpart' => $new->pkgpart,
- 'optionname' => $1,
- 'optionvalue' => $2,
- };
- }
- split("\n", $plandata)
- ) {
- my $error = $part_pkg_option->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- warn " replacing pkg_svc records" if $DEBUG;
- my $pkg_svc = $options{'pkg_svc'} || {};
- foreach my $part_svc ( qsearch('part_svc', {} ) ) {
- my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
- my $primary_svc = $options{'primary_svc'} == $part_svc->svcpart ? 'Y' : '';
-
- my $old_pkg_svc = qsearchs('pkg_svc', {
- 'pkgpart' => $old->pkgpart,
- 'svcpart' => $part_svc->svcpart,
- } );
- my $old_quantity = $old_pkg_svc ? $old_pkg_svc->quantity : 0;
- my $old_primary_svc =
- ( $old_pkg_svc && $old_pkg_svc->dbdef_table->column('primary_svc') )
- ? $old_pkg_svc->primary_svc
- : '';
- next unless $old_quantity != $quantity || $old_primary_svc ne $primary_svc;
-
- my $new_pkg_svc = new FS::pkg_svc( {
- 'pkgsvcnum' => ( $old_pkg_svc ? $old_pkg_svc->pkgsvcnum : '' ),
- 'pkgpart' => $new->pkgpart,
- 'svcpart' => $part_svc->svcpart,
- 'quantity' => $quantity,
- 'primary_svc' => $primary_svc,
- } );
- my $error = $old_pkg_svc
- ? $new_pkg_svc->replace($old_pkg_svc)
- : $new_pkg_svc->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- warn " commiting transaction" if $DEBUG;
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-=item check
-
-Checks all fields to make sure this is a valid package definition. If
-there is an error, returns the error, otherwise returns false. Called by the
-insert and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
- warn "FS::part_pkg::check called on $self" if $DEBUG;
-
- for (qw(setup recur plandata)) {
- #$self->set($_=>0) if $self->get($_) =~ /^\s*$/; }
- return "Use of $_ field is deprecated; set a plan and options"
- if length($self->get($_));
- $self->set($_, '');
- }
-
- if ( $self->dbdef_table->column('freq')->type =~ /(int)/i ) {
- my $error = $self->ut_number('freq');
- return $error if $error;
- } else {
- $self->freq =~ /^(\d+[hdw]?)$/
- or return "Illegal or empty freq: ". $self->freq;
- $self->freq($1);
- }
-
- my $error = $self->ut_numbern('pkgpart')
- || $self->ut_text('pkg')
- || $self->ut_text('comment')
- || $self->ut_textn('promo_code')
- || $self->ut_alphan('plan')
- || $self->ut_enum('setuptax', [ '', 'Y' ] )
- || $self->ut_enum('recurtax', [ '', 'Y' ] )
- || $self->ut_textn('taxclass')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- || $self->ut_floatn('pay_weight')
- || $self->ut_floatn('credit_weight')
- || $self->ut_agentnum_acl('agentnum', 'Edit global package definitions')
- || $self->SUPER::check
- ;
- return $error if $error;
-
- if ( $self->classnum !~ /^$/ ) {
- my $error = $self->ut_foreign_key('classnum', 'pkg_class', 'classnum');
- return $error if $error;
- } else {
- $self->classnum('');
- }
-
- return 'Unknown plan '. $self->plan
- unless exists($plans{$self->plan});
-
- my $conf = new FS::Conf;
- return 'Taxclass is required'
- if ! $self->taxclass && $conf->exists('require_taxclasses');
-
- '';
-}
-
-=item pkg_class
-
-Returns the package class, as an FS::pkg_class object, or the empty string
-if there is no package class.
-
-=cut
-
-sub pkg_class {
- my $self = shift;
- if ( $self->classnum ) {
- qsearchs('pkg_class', { 'classnum' => $self->classnum } );
- } else {
- return '';
- }
-}
-
-=item classname
-
-Returns the package class name, or the empty string if there is no package
-class.
-
-=cut
-
-sub classname {
- my $self = shift;
- my $pkg_class = $self->pkg_class;
- $pkg_class
- ? $pkg_class->classname
- : '';
-}
-
-=item agent
-
-Returns the associated agent for this event, if any, as an FS::agent object.
-
-=cut
-
-sub agent {
- my $self = shift;
- qsearchs('agent', { 'agentnum' => $self->agentnum } );
-}
-
-=item pkg_svc
-
-Returns all FS::pkg_svc objects (see L<FS::pkg_svc>) for this package
-definition (with non-zero quantity).
-
-=cut
-
-sub pkg_svc {
- my $self = shift;
- #sort { $b->primary cmp $a->primary }
- grep { $_->quantity }
- qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
-}
-
-=item svcpart [ SVCDB ]
-
-Returns the svcpart of the primary service definition (see L<FS::part_svc>)
-associated with this package definition (see L<FS::pkg_svc>). Returns
-false if there not a primary service definition or exactly one service
-definition with quantity 1, or if SVCDB is specified and does not match the
-svcdb of the service definition,
-
-=cut
-
-sub svcpart {
- my $self = shift;
- my $svcdb = scalar(@_) ? shift : '';
- my @svcdb_pkg_svc =
- grep { ( $svcdb eq $_->part_svc->svcdb || !$svcdb ) } $self->pkg_svc;
- my @pkg_svc = ();
- @pkg_svc = grep { $_->primary_svc =~ /^Y/i } @svcdb_pkg_svc
- if dbdef->table('pkg_svc')->column('primary_svc');
- @pkg_svc = grep {$_->quantity == 1 } @svcdb_pkg_svc
- unless @pkg_svc;
- return '' if scalar(@pkg_svc) != 1;
- $pkg_svc[0]->svcpart;
-}
-
-=item payby
-
-Returns a list of the acceptable payment types for this package. Eventually
-this should come out of a database table and be editable, but currently has the
-following logic instead:
-
-If the package is free, the single item B<BILL> is
-returned, otherwise, the single item B<CARD> is returned.
-
-(CHEK? LEC? Probably shouldn't accept those by default, prone to abuse)
-
-=cut
-
-sub payby {
- my $self = shift;
- if ( $self->is_free ) {
- ( 'BILL' );
- } else {
- ( 'CARD' );
- }
-}
-
-=item is_free
-
-Returns true if this package is free.
-
-=cut
-
-sub is_free {
- my $self = shift;
- unless ( $self->plan ) {
- $self->setup =~ /^\s*0+(\.0*)?\s*$/
- && $self->recur =~ /^\s*0+(\.0*)?\s*$/;
- } elsif ( $self->can('is_free_options') ) {
- not grep { $_ !~ /^\s*0*(\.0*)?\s*$/ }
- map { $self->option($_) }
- $self->is_free_options;
- } else {
- warn "FS::part_pkg::is_free: FS::part_pkg::". $self->plan. " subclass ".
- "provides neither is_free_options nor is_free method; returning false";
- 0;
- }
-}
-
-
-sub freqs_href {
- #method, class method or sub? #my $self = shift;
-
- tie my %freq, 'Tie::IxHash',
- '0' => '(no recurring fee)',
- '1h' => 'hourly',
- '1d' => 'daily',
- '2d' => 'every two days',
- '3d' => 'every three days',
- '1w' => 'weekly',
- '2w' => 'biweekly (every 2 weeks)',
- '1' => 'monthly',
- '45d' => 'every 45 days',
- '2' => 'bimonthly (every 2 months)',
- '3' => 'quarterly (every 3 months)',
- '4' => 'every 4 months',
- '137d' => 'every 4 1/2 months (137 days)',
- '6' => 'semiannually (every 6 months)',
- '12' => 'annually',
- '13' => 'every 13 months (annually +1 month)',
- '24' => 'biannually (every 2 years)',
- '36' => 'triannually (every 3 years)',
- '48' => '(every 4 years)',
- '60' => '(every 5 years)',
- '120' => '(every 10 years)',
- ;
-
- \%freq;
-
-}
-
-=item freq_pretty
-
-Returns an english representation of the I<freq> field, such as "monthly",
-"weekly", "semi-annually", etc.
-
-=cut
-
-sub freq_pretty {
- my $self = shift;
- my $freq = $self->freq;
-
- #my $freqs_href = $self->freqs_href;
- my $freqs_href = freqs_href();
-
- if ( exists($freqs_href->{$freq}) ) {
- $freqs_href->{$freq};
- } else {
- my $interval = 'month';
- if ( $freq =~ /^(\d+)([hdw])$/ ) {
- my %interval = ( 'h' => 'hour', 'd'=>'day', 'w'=>'week' );
- $interval = $interval{$2};
- }
- if ( $1 == 1 ) {
- "every $interval";
- } else {
- "every $freq ${interval}s";
- }
- }
-}
-
-=item plandata
-
-For backwards compatibility, returns the plandata field as well as all options
-from FS::part_pkg_option.
-
-=cut
-
-sub plandata {
- my $self = shift;
- carp "plandata is deprecated";
- if ( @_ ) {
- $self->SUPER::plandata(@_);
- } else {
- my $plandata = $self->get('plandata');
- my %options = $self->options;
- $plandata .= join('', map { "$_=$options{$_}\n" } keys %options );
- $plandata;
- }
-}
-
-=item part_pkg_option
-
-Returns all options as FS::part_pkg_option objects (see
-L<FS::part_pkg_option>).
-
-=cut
-
-sub part_pkg_option {
- my $self = shift;
- qsearch('part_pkg_option', { 'pkgpart' => $self->pkgpart } );
-}
-
-=item options
-
-Returns a list of option names and values suitable for assigning to a hash.
-
-=cut
-
-sub options {
- my $self = shift;
- map { $_->optionname => $_->optionvalue } $self->part_pkg_option;
-}
-
-=item option OPTIONNAME
-
-Returns the option value for the given name, or the empty string.
-
-=cut
-
-sub option {
- my( $self, $opt, $ornull ) = @_;
- my $part_pkg_option =
- qsearchs('part_pkg_option', {
- pkgpart => $self->pkgpart,
- optionname => $opt,
- } );
- return $part_pkg_option->optionvalue if $part_pkg_option;
- my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
- split("\n", $self->get('plandata') );
- return $plandata{$opt} if exists $plandata{$opt};
- cluck "WARNING: (pkgpart ". $self->pkgpart. ") Package def option $opt ".
- "not found in options or plandata!\n"
- unless $ornull;
- '';
-}
-
-=item _rebless
-
-Reblesses the object into the FS::part_pkg::PLAN class (if available), where
-PLAN is the object's I<plan> field. There should be better docs
-on how to create new price plans, but until then, see L</NEW PLAN CLASSES>.
-
-=cut
-
-sub _rebless {
- my $self = shift;
- my $plan = $self->plan;
- unless ( $plan ) {
- confess "no price plan found for pkgpart ". $self->pkgpart. "\n"
- if $DEBUG;
- return $self;
- }
- return $self if ref($self) =~ /::$plan$/; #already blessed into plan subclass
- my $class = ref($self). "::$plan";
- warn "reblessing $self into $class" if $DEBUG;
- eval "use $class;";
- die $@ if $@;
- bless($self, $class) unless $@;
- $self;
-}
-
-#fallbacks that eval the setup and recur fields, for backwards compat
-
-sub calc_setup {
- my $self = shift;
- warn 'no price plan class for '. $self->plan. ", eval-ing setup\n";
- $self->_calc_eval('setup', @_);
-}
-
-sub calc_recur {
- my $self = shift;
- warn 'no price plan class for '. $self->plan. ", eval-ing recur\n";
- $self->_calc_eval('recur', @_);
-}
-
-use vars qw( $sdate @details );
-sub _calc_eval {
- #my( $self, $field, $cust_pkg ) = @_;
- my( $self, $field, $cust_pkg, $sdateref, $detailsref ) = @_;
- *sdate = $sdateref;
- *details = $detailsref;
- $self->$field() =~ /^(.*)$/
- or die "Illegal $field (pkgpart ". $self->pkgpart. '): '.
- $self->$field(). "\n";
- my $prog = $1;
- return 0 if $prog =~ /^\s*$/;
- my $value = eval $prog;
- die $@ if $@;
- $value;
-}
-
-#fallback that return 0 for old legacy packages with no plan
-
-sub calc_remain { 0; }
-sub calc_cancel { 0; }
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item plan_info
-
-=cut
-
-my %info;
-foreach my $INC ( @INC ) {
- warn "globbing $INC/FS/part_pkg/*.pm\n" if $DEBUG;
- foreach my $file ( glob("$INC/FS/part_pkg/*.pm") ) {
- warn "attempting to load plan info from $file\n" if $DEBUG;
- $file =~ /\/(\w+)\.pm$/ or do {
- warn "unrecognized file in $INC/FS/part_pkg/: $file\n";
- next;
- };
- my $mod = $1;
- my $info = eval "use FS::part_pkg::$mod; ".
- "\\%FS::part_pkg::$mod\::info;";
- if ( $@ ) {
- die "error using FS::part_pkg::$mod (skipping): $@\n" if $@;
- next;
- }
- unless ( keys %$info ) {
- warn "no %info hash found in FS::part_pkg::$mod, skipping\n"
- unless $mod =~ /^(passwdfile|null)$/; #hack but what the heck
- next;
- }
- warn "got plan info from FS::part_pkg::$mod: $info\n" if $DEBUG;
- if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
- warn "skipping disabled plan FS::part_pkg::$mod" if $DEBUG;
- next;
- }
- $info{$mod} = $info;
- }
-}
-
-tie %plans, 'Tie::IxHash',
- map { $_ => $info{$_} }
- sort { $info{$a}->{'weight'} <=> $info{$b}->{'weight'} }
- keys %info;
-
-sub plan_info {
- \%plans;
-}
-
-=item format OPTION DATA
-
-Returns data formatted according to the function 'format' described
-in the plan info. Returns DATA if no such function exists.
-
-=cut
-
-sub format {
- my ($self, $option, $data) = (shift, shift, shift);
- if (exists($plans{$self->plan}->{fields}->{$option}{format})) {
- &{$plans{$self->plan}->{fields}->{$option}{format}}($data);
- }else{
- $data;
- }
-}
-
-=item parse OPTION DATA
-
-Returns data parsed according to the function 'parse' described
-in the plan info. Returns DATA if no such function exists.
-
-=cut
-
-sub parse {
- my ($self, $option, $data) = (shift, shift, shift);
- if (exists($plans{$self->plan}->{fields}->{$option}{parse})) {
- &{$plans{$self->plan}->{fields}->{$option}{parse}}($data);
- }else{
- $data;
- }
-}
-
-
-=back
-
-=head1 NEW PLAN CLASSES
-
-A module should be added in FS/FS/part_pkg/ Eventually, an example may be
-found in eg/plan_template.pm. Until then, it is suggested that you use the
-other modules in FS/FS/part_pkg/ as a guide.
-
-=head1 BUGS
-
-The delete method is unimplemented.
-
-setup and recur semantics are not yet defined (and are implemented in
-FS::cust_bill. hmm.). now they're deprecated and need to go.
-
-plandata should go
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_pkg>, L<FS::type_pkgs>, L<FS::pkg_svc>, L<Safe>.
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_pkg/base_delayed.pm b/FS/FS/part_pkg/base_delayed.pm
deleted file mode 100644
index ddd4caf..0000000
--- a/FS/FS/part_pkg/base_delayed.pm
+++ /dev/null
@@ -1,51 +0,0 @@
-package FS::part_pkg::base_delayed;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::base_rate;
-
-@ISA = qw(FS::part_pkg::base_rate);
-
-%info = (
- 'name' => 'Free (or setup fee) for X days, then base rate'.
- ' (anniversary billing)',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'free_days' => { 'name' => 'Initial free days',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring base fee for this package',
- 'default' => 0,
- },
- 'recur_notify' => { 'name' => 'Number of days before recurring billing'.
- 'commences to notify customer. (0 means '.
- 'no warning)',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- },
- 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'recur_notify',
- 'unused_credit'
- ],
- #'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value',
- #'recur' => 'what.recur_fee.value',
- 'weight' => 50,
-);
-
-sub calc_setup {
- my($self, $cust_pkg, $time ) = @_;
-
- my $d = $cust_pkg->bill || $time;
- $d += 86400 * $self->option('free_days');
- $cust_pkg->bill($d);
-
- $self->option('setup_fee');
-}
-
-1;
diff --git a/FS/FS/part_pkg/base_rate.pm b/FS/FS/part_pkg/base_rate.pm
deleted file mode 100644
index 04896e0..0000000
--- a/FS/FS/part_pkg/base_rate.pm
+++ /dev/null
@@ -1,93 +0,0 @@
-package FS::part_pkg::base_rate;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch);
-use FS::part_pkg;
-
-@ISA = qw(FS::part_pkg);
-
-%info = (
- 'name' => 'Base rate (anniversary billing, Times units ordered)',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring Base fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'externalid' => { 'name' => 'Optional External ID',
- 'default' => '',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit',
- 'externalid' ],
- 'weight' => 10,
-);
-
-sub calc_setup {
- my($self, $cust_pkg, $sdate, $details ) = @_;
-
- my $i = 0;
- my $count = $self->option( 'additional_count', 'quiet' ) || 0;
- while ($i < $count) {
- push @$details, $self->option( 'additional_info' . $i++ );
- }
-
- $self->option('setup_fee');
-}
-
-sub calc_recur {
- my($self, $cust_pkg) = @_;
- $self->base_recur($cust_pkg);
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- my $units = $cust_pkg->option('units') ? $cust_pkg->option('units') : 1 ;
- # default to 1 if not found
- sprintf("%.2f",
- ($self->option('recur_fee') * $units )
- );
-}
-
-sub calc_remain {
- my ($self, $cust_pkg) = @_;
- my $time = time; #should be able to pass this in for credit calculation
- my $next_bill = $cust_pkg->getfield('bill') || 0;
- my $last_bill = $cust_pkg->last_bill || 0;
- return 0 if ! $self->base_recur
- || ! $self->option('unused_credit', 1)
- || ! $last_bill
- || ! $next_bill
- || $next_bill < $time;
-
- my %sec = (
- 'h' => 3600, # 60 * 60
- 'd' => 86400, # 60 * 60 * 24
- 'w' => 604800, # 60 * 60 * 24 * 7
- 'm' => 2629744, # 60 * 60 * 24 * 365.2422 / 12
- );
-
- $self->freq =~ /^(\d+)([hdwm]?)$/
- or die 'unparsable frequency: '. $self->freq;
- my $freq_sec = $1 * $sec{$2||'m'};
- return 0 unless $freq_sec;
-
- sprintf("%.2f", $self->base_recur * ( $next_bill - $time ) / $freq_sec );
-
-}
-
-sub is_free_options {
- qw( setup_fee recur_fee );
-}
-
-sub is_prepaid {
- 0; #no, we're postpaid
-}
-
-1;
diff --git a/FS/FS/part_pkg/bulk.pm b/FS/FS/part_pkg/bulk.pm
deleted file mode 100644
index 44645b7..0000000
--- a/FS/FS/part_pkg/bulk.pm
+++ /dev/null
@@ -1,96 +0,0 @@
-package FS::part_pkg::bulk;
-
-use strict;
-use vars qw(@ISA $DEBUG $me %info);
-use Date::Format;
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-$DEBUG = 0;
-$me = '[FS::part_pkg::bulk]';
-
-%info = (
- 'name' => 'Bulk billing based on number of active services',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for the entire bulk package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for the entire bulk package',
- 'default' => 0,
- },
- 'svc_setup_fee' => { 'name' => 'Setup fee for each new service',
- 'default' => 0,
- },
- 'svc_recur_fee' => { 'name' => 'Recurring fee for each service',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'svc_setup_fee', 'svc_recur_fee',
- 'unused_credit', ],
- 'weight' => 55,
-);
-
-sub calc_recur {
- my($self, $cust_pkg, $sdate, $details ) = @_;
-
- my $conf = new FS::Conf;
- my $money_char = $conf->config('money_char') || '$';
-
- my $svc_setup_fee = $self->option('svc_setup_fee');
-
- my $last_bill = $cust_pkg->last_bill;
-
- my $total_svc_charge = 0;
-
- warn "$me billing for bulk services from ". time2str('%x', $last_bill).
- " to ". time2str('%x', $$sdate). "\n"
- if $DEBUG;
-
- # END START
- foreach my $h_svc ( $cust_pkg->h_cust_svc( $$sdate, $last_bill ) ) {
-
- my @label = $h_svc->label( $$sdate, $last_bill );
- die "fatal: no historical label found, wtf?" unless scalar(@label); #?
- #my $svc_details = $label[0].': '. $label[1]. ': ';
- my $svc_details = $label[1]. ': ';
-
- my $svc_charge = 0;
-
- my $svc_start = $h_svc->date_inserted;
- if ( $svc_start < $last_bill ) {
- $svc_start = $last_bill;
- } elsif ( $svc_setup_fee ) {
- $svc_charge += $svc_setup_fee;
- $svc_details .= $money_char. sprintf('%.2f setup, ', $svc_setup_fee);
- }
-
- my $svc_end = $h_svc->date_deleted;
- $svc_end = ( !$svc_end || $svc_end > $$sdate ) ? $$sdate : $svc_end;
-
- $svc_charge = $self->option('svc_recur_fee') * ( $svc_end - $svc_start )
- / ( $$sdate - $last_bill );
-
- $svc_details .= $money_char. sprintf('%.2f', $svc_charge ).
- ' ('. time2str('%x', $svc_start).
- ' - '. time2str('%x', $svc_end ). ')'
- if $self->option('svc_recur_fee');
-
- push @$details, $svc_details;
- $total_svc_charge += $svc_charge;
-
- }
-
- sprintf("%.2f", $self->base_recur($cust_pkg) + $total_svc_charge );
-}
-
-sub is_free_options {
- qw( setup_fee recur_fee svc_setup_fee svc_recur_fee );
-}
-
-1;
-
diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm
deleted file mode 100644
index 92e72cf..0000000
--- a/FS/FS/part_pkg/flat.pm
+++ /dev/null
@@ -1,168 +0,0 @@
-package FS::part_pkg::flat;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch);
-use FS::UI::bytecount;
-use FS::part_pkg;
-
-@ISA = qw(FS::part_pkg);
-
-%info = (
- 'name' => 'Flat rate (anniversary billing)',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'externalid' => { 'name' => 'Optional External ID',
- 'default' => '',
- },
- 'seconds' => { 'name' => 'Time limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- },
- 'upbytes' => { 'name' => 'Upload limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'downbytes' => { 'name' => 'Download limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'totalbytes' => { 'name' => 'Transfer limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_amount' => { 'name' => 'Cost of recharge for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*(\.\d{2})?$/ },
- },
- 'recharge_seconds' => { 'name' => 'Recharge time for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- },
- 'recharge_upbytes' => { 'name' => 'Recharge upload for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_downbytes' => { 'name' => 'Recharge download for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '.
- ' over into current period',
- 'type' => 'checkbox',
- },
- 'recharge_reset' => { 'name' => 'Reset usage to these values on manual '.
- 'package recharge',
- 'type' => 'checkbox',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit',
- 'seconds', 'upbytes', 'downbytes', 'totalbytes',
- 'recharge_amount', 'recharge_seconds', 'recharge_upbytes',
- 'recharge_downbytes', 'recharge_totalbytes',
- 'usage_rollover', 'recharge_reset', 'externalid' ],
- 'weight' => 10,
-);
-
-sub calc_setup {
- my($self, $cust_pkg, $sdate, $details ) = @_;
-
- my $i = 0;
- my $count = $self->option( 'additional_count', 'quiet' ) || 0;
- while ($i < $count) {
- push @$details, $self->option( 'additional_info' . $i++ );
- }
-
- $self->option('setup_fee');
-}
-
-sub calc_recur {
- my($self, $cust_pkg) = @_;
- $self->base_recur($cust_pkg);
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- $self->option('recur_fee', 1) || 0;
-}
-
-sub calc_remain {
- my ($self, $cust_pkg, %options) = @_;
-
- my $time;
- if ($options{'time'}) {
- $time = $options{'time'};
- } else {
- $time = time;
- }
-
- my $next_bill = $cust_pkg->getfield('bill') || 0;
- my $last_bill = $cust_pkg->last_bill || 0;
- return 0 if ! $self->base_recur
- || ! $self->option('unused_credit', 1)
- || ! $last_bill
- || ! $next_bill
- || $next_bill < $time;
-
- my %sec = (
- 'h' => 3600, # 60 * 60
- 'd' => 86400, # 60 * 60 * 24
- 'w' => 604800, # 60 * 60 * 24 * 7
- 'm' => 2629744, # 60 * 60 * 24 * 365.2422 / 12
- );
-
- $self->freq =~ /^(\d+)([hdwm]?)$/
- or die 'unparsable frequency: '. $self->freq;
- my $freq_sec = $1 * $sec{$2||'m'};
- return 0 unless $freq_sec;
-
- sprintf("%.2f", $self->base_recur * ( $next_bill - $time ) / $freq_sec );
-
-}
-
-sub is_free_options {
- qw( setup_fee recur_fee );
-}
-
-sub is_prepaid {
- 0; #no, we're postpaid
-}
-
-sub reset_usage {
- my($self, $cust_pkg) = @_;
- my %values = map { $_, $self->option($_) }
- grep { $self->option($_, 'hush') }
- qw(seconds upbytes downbytes totalbytes);
- if ($self->option('usage_rollover', 1)) {
- $cust_pkg->recharge(\%values);
- }else{
- $cust_pkg->set_usage(\%values);
- }
-}
-
-1;
diff --git a/FS/FS/part_pkg/flat_comission.pm b/FS/FS/part_pkg/flat_comission.pm
deleted file mode 100644
index 4592bed..0000000
--- a/FS/FS/part_pkg/flat_comission.pm
+++ /dev/null
@@ -1,66 +0,0 @@
-package FS::part_pkg::flat_comission;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Flat rate with recurring commission per (any) active package',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'comission_amount' => { 'name' => 'Commission amount per month (per active package)',
- 'default' => 0,
- },
- 'comission_depth' => { 'name' => 'Number of layers',
- 'default' => 1,
- },
- 'reason_type' => { 'name' => 'Reason type for commission credits',
- 'type' => 'select',
- 'select_table' => 'reason_type',
- 'select_hash' => { 'class' => 'R' },
- 'select_key' => 'typenum',
- 'select_label' => 'type',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'comission_depth', 'comission_amount', 'reason_type' ],
- #'setup' => 'what.setup_fee.value',
- #'recur' => '\'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar($cust_pkg->cust_main->referral_cust_pkg(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'',
- 'weight' => 62,
-);
-
-sub calc_recur {
- my($self, $cust_pkg ) = @_;
-
- my $amount = $self->option('comission_amount');
- my $num_active = scalar(
- $cust_pkg->cust_main->referral_cust_pkg( $self->option('comission_depth') )
- );
-
- my $commission = sprintf('%.2f', $amount*$num_active);
-
- if ( $commission > 0 ) {
-
- my $error =
- $cust_pkg->cust_main->credit( $commission, "commission",
- 'reason_type'=>$self->option('reason_type'),
- );
- die $error if $error;
-
- }
-
- $self->option('recur_fee');
-}
-
-1;
diff --git a/FS/FS/part_pkg/flat_comission_cust.pm b/FS/FS/part_pkg/flat_comission_cust.pm
deleted file mode 100644
index 82e5111..0000000
--- a/FS/FS/part_pkg/flat_comission_cust.pm
+++ /dev/null
@@ -1,64 +0,0 @@
-package FS::part_pkg::flat_comission_cust;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Flat rate with recurring commission per active customer',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'comission_amount' => { 'name' => 'Commission amount per month (per active customer)',
- 'default' => 0,
- },
- 'comission_depth' => { 'name' => 'Number of layers',
- 'default' => 1,
- },
- 'reason_type' => { 'name' => 'Reason type for commission credits',
- 'type' => 'select_table',
- 'select_table' => 'reason_type',
- 'select_hash' => { 'class' => 'R' },
- 'select_key' => 'typenum',
- 'select_label' => 'type',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'comission_depth', 'comission_amount', 'reason_type' ],
- #'setup' => 'what.setup_fee.value',
- #'recur' => '\'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar($cust_pkg->cust_main->referral_cust_main_ncancelled(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'',
- 'weight' => '60',
-);
-
-sub calc_recur {
- my($self, $cust_pkg ) = @_;
-
- my $amount = $self->option('comission_amount');
- my $num_active = scalar(
- $cust_pkg->cust_main->referral_cust_main_ncancelled(
- $self->option('comission_depth')
- )
- );
-
- if ( $amount && $num_active ) {
- my $error =
- $cust_pkg->cust_main->credit( $amount*$num_active, "commission",
- 'reason_type'=>$self->option('reason_type'),
- );
- die $error if $error;
- }
-
- $self->option('recur_fee');
-}
-
-1;
diff --git a/FS/FS/part_pkg/flat_comission_pkg.pm b/FS/FS/part_pkg/flat_comission_pkg.pm
deleted file mode 100644
index 07c3d1b..0000000
--- a/FS/FS/part_pkg/flat_comission_pkg.pm
+++ /dev/null
@@ -1,57 +0,0 @@
-package FS::part_pkg::flat_comission_pkg;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Flat rate with recurring commission per (selected) active package',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'comission_amount' => { 'name' => 'Commission amount per month (per uncancelled package)',
- 'default' => 0,
- },
- 'comission_depth' => { 'name' => 'Number of layers',
- 'default' => 1,
- },
- 'comission_pkgpart' => { 'name' => 'Applicable packages<BR><FONT SIZE="-1">(hold <b>ctrl</b> to select multiple packages)</FONT>',
- 'type' => 'select_multiple',
- 'select_table' => 'part_pkg',
- 'select_hash' => { 'disabled' => '' } ,
- 'select_key' => 'pkgpart',
- 'select_label' => 'pkg',
- },
- 'reason_type' => { 'name' => 'Reason type for commission credits',
- 'type' => 'select',
- 'select_table' => 'reason_type',
- 'select_hash' => { 'class' => 'R' } ,
- 'select_key' => 'typenum',
- 'select_label' => 'type',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'comission_depth', 'comission_amount', 'comission_pkgpart', 'reason_type' ],
- #'setup' => 'what.setup_fee.value',
- #'recur' => '""; var pkgparts = ""; for ( var c=0; c < document.flat_comission_pkg.comission_pkgpart.options.length; c++ ) { if (document.flat_comission_pkg.comission_pkgpart.options[c].selected) { pkgparts = pkgparts + document.flat_comission_pkg.comission_pkgpart.options[c].value + \', \'; } } what.recur.value = \'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar( grep { my $pkgpart = $_->pkgpart; grep { $_ == $pkgpart } ( \' + pkgparts + \' ) } $cust_pkg->cust_main->referral_cust_pkg(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'',
- #'disabled' => 1,
- 'weight' => '64',
-);
-
-# XXX this needs to be fixed!!!
-sub calc_recur {
- my($self, $cust_pkg ) = @_;
- $self->option('recur_fee');
-}
-
-1;
diff --git a/FS/FS/part_pkg/flat_delayed.pm b/FS/FS/part_pkg/flat_delayed.pm
deleted file mode 100644
index 8ac1682..0000000
--- a/FS/FS/part_pkg/flat_delayed.pm
+++ /dev/null
@@ -1,68 +0,0 @@
-package FS::part_pkg::flat_delayed;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Free (or setup fee) for X days, then flat rate'.
- ' (anniversary billing)',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'free_days' => { 'name' => 'Initial free days',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'recur_notify' => { 'name' => 'Number of days before recurring billing'.
- 'commences to notify customer. (0 means '.
- 'no warning)',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- },
- 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'recur_notify',
- 'unused_credit'
- ],
- #'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value',
- #'recur' => 'what.recur_fee.value',
- 'weight' => 50,
-);
-
-sub calc_setup {
- my($self, $cust_pkg, $time ) = @_;
-
- my $d = $cust_pkg->bill || $time;
- $d += 86400 * $self->option('free_days');
- $cust_pkg->bill($d);
-
- $self->option('setup_fee');
-}
-
-sub calc_remain {
- my ($self, $cust_pkg, %options) = @_;
- my $next_bill = $cust_pkg->getfield('bill') || 0;
- my $last_bill = $cust_pkg->last_bill || 0;
- my $free_days = $self->option('free_days');
-
- return 0 if $last_bill + (86400 * $free_days) == $next_bill
- && $last_bill == $cust_pkg->setup;
-
- return 0 if ! $self->base_recur
- || ! $self->option('unused_credit', 1)
- || ! $last_bill
- || ! $next_bill;
-
- return $self->SUPER::calc_remain($cust_pkg, %options);
-}
-
-1;
diff --git a/FS/FS/part_pkg/flat_introrate.pm b/FS/FS/part_pkg/flat_introrate.pm
deleted file mode 100644
index c92ba97..0000000
--- a/FS/FS/part_pkg/flat_introrate.pm
+++ /dev/null
@@ -1,67 +0,0 @@
-package FS::part_pkg::flat_introrate;
-
-use strict;
-use vars qw(@ISA %info $DEBUG $DEBUG_PRE);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-use Date::Manip qw(DateCalc UnixDate ParseDate);
-
-@ISA = qw(FS::part_pkg::flat);
-$DEBUG = 0;
-$DEBUG_PRE = '[' . __PACKAGE__ . ']: ';
-
-%info = (
- 'name' => 'Introductory price for X months, then flat rate,'.
- 'relative to setup date (anniversary billing)',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'intro_fee' => { 'name' => 'Introductory recurring free for this package',
- 'default' => 0,
- },
- 'intro_duration' => { 'name' => 'Duration of the introductory period, ' .
- 'in number of months',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'intro_duration', 'intro_fee', 'recur_fee', 'unused_credit' ],
- 'weight' => 150,
-);
-
-sub calc_recur {
- my($self, $cust_pkg, $time ) = @_;
-
- my ($duration) = ($self->option('intro_duration') =~ /^(\d+)$/);
- unless ($duration) {
- die "Invalid intro_duration: " . $self->option('intro_duration');
- }
-
- my $setup = &ParseDate('epoch ' . $cust_pkg->getfield('setup'));
- my $intro_end = &DateCalc($setup, "+${duration} month");
- my $recur;
-
- warn $DEBUG_PRE . "\$duration = ${duration}" if $DEBUG;
- warn $DEBUG_PRE . "\$intro_end = ${intro_end}" if $DEBUG;
- warn $DEBUG_PRE . "$$time < " . &UnixDate($intro_end, '%s') if $DEBUG;
-
- if ($$time < &UnixDate($intro_end, '%s')) {
- $recur = $self->option('intro_fee');
- } else {
- $recur = $self->option('recur_fee');
- }
-
- $recur;
-
-}
-
-
-1;
diff --git a/FS/FS/part_pkg/incomplete/billoneday.pm b/FS/FS/part_pkg/incomplete/billoneday.pm
deleted file mode 100644
index 8740547..0000000
--- a/FS/FS/part_pkg/incomplete/billoneday.pm
+++ /dev/null
@@ -1,48 +0,0 @@
-package FS::part_pkg::billoneday;
-
-use strict;
-use vars qw(@ISA %info);
-use Time::Local qw(timelocal);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'charge a full month every (selectable) billing day',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'cutoff_day' => { 'name' => 'billing day',
- 'default' => 1,
- },
-
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day'],
- #'setup' => 'what.setup_fee.value',
- #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); \' + what.recur_fee.value',
- 'freq' => 'm',
- 'weight' => 30,
-);
-
-sub calc_recur {
- my($self, $cust_pkg, $sdate ) = @_;
-
- my $mnow = $$sdate;
- my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5];
- my $mstart = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year);
- my $mend = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11));
-
- if($mday > $self->option('cutoff_date') and $mstart != $mnow ) {
- $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11));
- }
- else{
- $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon, $year);
- }
- $self->option('recur_fee');
-}
-1;
diff --git a/FS/FS/part_pkg/prepaid.pm b/FS/FS/part_pkg/prepaid.pm
deleted file mode 100644
index d309d45..0000000
--- a/FS/FS/part_pkg/prepaid.pm
+++ /dev/null
@@ -1,38 +0,0 @@
-package FS::part_pkg::prepaid;
-
-use strict;
-use vars qw(@ISA %info %recur_action);
-use Tie::IxHash;
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-tie %recur_action, 'Tie::IxHash',
- 'suspend' => 'suspend',
- 'cancel' => 'cancel',
-;
-
-%info = (
- 'name' => 'Prepaid, flat rate',
- 'fields' => {
- 'setup_fee' => { 'name' => 'One-time setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Initial and recharge fee for this package',
- 'default' => 0,
- },
- 'recur_action' => { 'name' => 'Action to take upon reaching end of prepaid preiod',
- 'type' => 'select',
- 'select_options' => \%recur_action,
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'recur_action', ],
- 'weight' => 25,
-);
-
-sub is_prepaid {
- 1;
-}
-
-1;
-
diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm
deleted file mode 100644
index 45bbf01..0000000
--- a/FS/FS/part_pkg/prorate.pm
+++ /dev/null
@@ -1,122 +0,0 @@
-package FS::part_pkg::prorate;
-
-use strict;
-use vars qw(@ISA %info);
-use Time::Local qw(timelocal);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'First partial month pro-rated, then flat-rate (selectable billing day)',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'cutoff_day' => { 'name' => 'Billing Day (1 - 28)',
- 'default' => 1,
- },
- 'seconds' => { 'name' => 'Time limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- },
- 'upbytes' => { 'name' => 'Upload limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'downbytes' => { 'name' => 'Download limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'totalbytes' => { 'name' => 'Transfer limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_amount' => { 'name' => 'Cost of recharge for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*(\.\d{2})?$/ },
- },
- 'recharge_seconds' => { 'name' => 'Recharge time for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- },
- 'recharge_upbytes' => { 'name' => 'Recharge upload for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_downbytes' => { 'name' => 'Recharge download for this package', 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '.
- 'over into current period',
- 'type' => 'checkbox',
- },
- 'recharge_reset' => { 'name' => 'Reset usage to these values on manual '.
- 'package recharge',
- 'type' => 'checkbox',
- },
-
- #it would be better if this had to be turned on, its confusing
- 'externalid' => { 'name' => 'Optional External ID',
- 'default' => '',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'cutoff_day',
- 'seconds', 'upbyte', 'downbytes', 'totalbytes',
- 'recharge_amount', 'recharge_seconds', 'recharge_upbytes',
- 'recharge_downbytes', 'recharge_totalbytes',
- 'usage_rollover', 'recharge_reset', 'externalid', ],
- 'freq' => 'm',
- 'weight' => 20,
-);
-
-sub calc_recur {
- my($self, $cust_pkg, $sdate ) = @_;
- my $cutoff_day = $self->option('cutoff_day', 1) || 1;
- my $mnow = $$sdate;
- my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5];
- my $mend;
- my $mstart;
-
- if ( $mday >= $cutoff_day ) {
- $mend =
- timelocal(0,0,0,$cutoff_day, $mon == 11 ? 0 : $mon+1, $year+($mon==11));
- $mstart =
- timelocal(0,0,0,$cutoff_day,$mon,$year);
-
- } else {
- $mend = timelocal(0,0,0,$cutoff_day, $mon, $year);
- if ($mon==0) {$mon=11;$year--;} else {$mon--;}
- $mstart= timelocal(0,0,0,$cutoff_day,$mon,$year);
- }
-
- $$sdate = $mstart;
- my $permonth = $self->option('recur_fee') / $self->freq;
-
- $permonth * ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) );
-}
-
-1;
diff --git a/FS/FS/part_pkg/prorate_delayed.pm b/FS/FS/part_pkg/prorate_delayed.pm
deleted file mode 100644
index ee66432..0000000
--- a/FS/FS/part_pkg/prorate_delayed.pm
+++ /dev/null
@@ -1,61 +0,0 @@
-package FS::part_pkg::prorate_delayed;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg;
-
-@ISA = qw(FS::part_pkg::prorate);
-
-%info = (
- 'name' => 'Free (or setup fee) for X days, then prorate, then flat-rate ' .
- '(1st of month billing)',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'free_days' => { 'name' => 'Initial free days',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- },
- 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'unused_credit' ],
- #'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value',
- #'recur' => 'what.recur_fee.value',
- 'weight' => 50,
-);
-
-sub calc_setup {
- my($self, $cust_pkg, $time ) = @_;
-
- my $d = $cust_pkg->bill || $time;
- $d += 86400 * $self->option('free_days');
- $cust_pkg->bill($d);
-
- $self->option('setup_fee');
-}
-
-sub calc_remain {
- my ($self, $cust_pkg, %options) = @_;
- my $next_bill = $cust_pkg->getfield('bill') || 0;
- my $last_bill = $cust_pkg->last_bill || 0;
- my $free_days = $self->option('free_days');
-
- return 0 if $last_bill + (86400 * $free_days) == $next_bill
- && $last_bill == $cust_pkg->setup;
-
- return 0 if ! $self->base_recur
- || ! $self->option('unused_credit', 1)
- || ! $last_bill
- || ! $next_bill;
-
- return $self->SUPER::calc_remain($cust_pkg, %options);
-}
-
-1;
diff --git a/FS/FS/part_pkg/sesmon_hour.pm b/FS/FS/part_pkg/sesmon_hour.pm
deleted file mode 100644
index 9843edb..0000000
--- a/FS/FS/part_pkg/sesmon_hour.pm
+++ /dev/null
@@ -1,56 +0,0 @@
-package FS::part_pkg::sesmon_hour;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Base charge plus charge per-hour from the session monitor',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_flat' => { 'name' => 'Base recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'recur_included_hours' => { 'name' => 'Hours included',
- 'default' => 0,
- },
- 'recur_hourly_charge' => { 'name' => 'Additional charge per hour',
- 'default' => 0,
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_flat', 'unused_credit', 'recur_included_hours', 'recur_hourly_charge' ],
- #'setup' => 'what.setup_fee.value',
- #'recur' => '\'my $hours = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; \' + what.recur_flat.value + \' + \' + what.recur_hourly_charge.value + \' * $hours;\'',
- 'weight' => 80,
-);
-
-sub calc_recur {
- my($self, $cust_pkg ) = @_;
-
- my $hours = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 3600;
- $hours -= $self->option('recur_included_hours');
- $hours = 0 if $hours < 0;
-
- $self->option('recur_flat') + $hours * $self->option('recur_hourly_charge');
-
-}
-
-sub is_free_options {
- qw( setup_fee recur_fee recur_hourly_charge );
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- $self->option('recur_flat');
-}
-
-1;
diff --git a/FS/FS/part_pkg/sesmon_minute.pm b/FS/FS/part_pkg/sesmon_minute.pm
deleted file mode 100644
index 39516f8..0000000
--- a/FS/FS/part_pkg/sesmon_minute.pm
+++ /dev/null
@@ -1,55 +0,0 @@
-package FS::part_pkg::sesmon_minute;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Base charge plus charge per-minute from the session monitor',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_flat' => { 'name' => 'Base recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'recur_included_min' => { 'name' => 'Minutes included',
- 'default' => 0,
- },
- 'recur_minly_charge' => { 'name' => 'Additional charge per minute',
- 'default' => 0,
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_flat', 'unused_credit', 'recur_included_min', 'recur_minly_charge' ],
- #'setup' => 'what.setup_fee.value',
- #'recur' => '\'my $min = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 60 - \' + what.recur_included_min.value + \'; $min = 0 if $min < 0; \' + what.recur_flat.value + \' + \' + what.recur_minly_charge.value + \' * $min;\'',
- 'weight' => 80,
-);
-
-
-sub calc_recur {
- my( $self, $cust_pkg ) = @);
- my $min = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 60;
- $min -= $self->option('recur_included_min');
- $min = 0 if $min < 0;
-
- $self->option('recur_flat') + $min * $self->option('recur_minly_charge');
-}
-
-sub is_free_options {
- qw( setup_fee recur_fee recur_minly_charge );
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- $self->option('recur_flat');
-}
-
-1;
diff --git a/FS/FS/part_pkg/sql_external.pm b/FS/FS/part_pkg/sql_external.pm
deleted file mode 100644
index ca58c4e..0000000
--- a/FS/FS/part_pkg/sql_external.pm
+++ /dev/null
@@ -1,76 +0,0 @@
-package FS::part_pkg::sql_external;
-
-use strict;
-use vars qw(@ISA %info);
-use DBI;
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Base charge plus additional fees for external services from a configurable SQL query',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_flat' => { 'name' => 'Base recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'datasrc' => { 'name' => 'DBI data source',
- 'default' => '',
- },
- 'db_username' => { 'name' => 'Database username',
- 'default' => '',
- },
- 'db_password' => { 'name' => 'Database password',
- 'default' => '',
- },
- 'query' => { 'name' => 'SQL query',
- 'default' => '',
- },
- },
- 'fieldorder' => [qw( setup_fee recur_flat unused_credit datasrc db_username db_password query )],
- #'setup' => 'what.setup_fee.value',
- #'recur' => q!'my $dbh = DBI->connect("' + what.datasrc.value + '", "' + what.db_username.value + '", "' + what.db_password.value + '" ) or die $DBI::errstr; my $sth = $dbh->prepare("' + what.query.value + '") or die $dbh->errstr; my $price = ' + what.recur_flat.value + '; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_external" } $cust_pkg->cust_svc ){ my $id = $cust_svc->svc_x->id; $sth->execute($id) or die $sth->errstr; $price += $sth->fetchrow_arrayref->[0]; } $price;'!,
- 'weight' => '72',
-);
-
-sub calc_recur {
- my($self, $cust_pkg ) = @_;
-
- my $dbh = DBI->connect( map { $self->option($_) }
- qw( datasrc db_username db_password )
- )
- or die $DBI::errstr;
-
- my $sth = $dbh->prepare( $self->option('query') )
- or die $dbh->errstr;
-
- my $price = $self->option('recur_flat');
-
- foreach my $cust_svc (
- grep { $_->part_svc->svcdb eq "svc_external" } $cust_pkg->cust_svc
- ) {
- my $id = $cust_svc->svc_x->id;
- $sth->execute($id) or die $sth->errstr;
- $price += $sth->fetchrow_arrayref->[0];
- }
-
- $price;
-}
-
-sub is_free {
- 0;
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- $self->option('recur_flat');
-}
-
-1;
diff --git a/FS/FS/part_pkg/sql_generic.pm b/FS/FS/part_pkg/sql_generic.pm
deleted file mode 100644
index 0e6ab7c..0000000
--- a/FS/FS/part_pkg/sql_generic.pm
+++ /dev/null
@@ -1,87 +0,0 @@
-package FS::part_pkg::sql_generic;
-
-use strict;
-use vars qw(@ISA %info);
-use DBI;
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Base charge plus a per-domain metered rate from a configurable SQL query',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_flat' => { 'name' => 'Base recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'recur_included' => { 'name' => 'Units included',
- 'default' => 0,
- },
- 'recur_unit_charge' => { 'name' => 'Additional charge per unit',
- 'default' => 0,
- },
- 'datasrc' => { 'name' => 'DBI data source',
- 'default' => '',
- },
- 'db_username' => { 'name' => 'Database username',
- 'default' => '',
- },
- 'db_password' => { 'name' => 'Database username',
- 'default' => '',
- },
- 'query' => { 'name' => 'SQL query',
- 'default' => '',
- },
- },
- 'fieldorder' => [qw( setup_fee recur_flat unused_credit recur_included recur_unit_charge datasrc db_username db_password query )],
- # 'setup' => 'what.setup_fee.value',
- # 'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\") or die $DBI::errstr; \'',
- #'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\", \"\' + what.db_password.value + \'\" ) or die $DBI::errstr; my $sth = $dbh->prepare(\"\' + what.query.value + \'\") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq \"svc_domain\" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge.value + \';\'',
- #'recur' => '\'my $dbh = DBI->connect("\' + what.datasrc.value + \'", "\' + what.db_username.value + \'", "\' what.db_password.value + \'" ) or die $DBI::errstr; my $sth = $dbh->prepare("\' + what.query.value + \'") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_domain" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge + \';\'',
- 'weight' => '70',
-);
-
-sub calc_recur {
- my($self, $cust_pkg ) = @_;
-
- my $dbh = DBI->connect( map { $self->option($_) }
- qw( datasrc db_username db_password )
- )
- or die $DBI::errstr;
-
- my $sth = $dbh->prepare( $self->option('query') )
- or die $dbh->errstr;
-
- my $units = 0;
- foreach my $cust_svc (
- grep { $_->part_svc->svcdb eq "svc_domain" } $cust_pkg->cust_svc
- ) {
- my $domain = $cust_svc->svc_x->domain;
- $sth->execute($domain) or die $sth->errstr;
-
- $units += $sth->fetchrow_arrayref->[0];
- }
-
- $units -= $self->option('recur_included');
- $units = 0 if $units < 0;
-
- $self->option('recur_flat') + $units * $self->option('recur_unit_charge');
-}
-
-sub is_free_options {
- qw( setup_fee recur_flat recur_unit_charge );
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- $self->option('recur_flat');
-}
-
-1;
diff --git a/FS/FS/part_pkg/sqlradacct_hour.pm b/FS/FS/part_pkg/sqlradacct_hour.pm
deleted file mode 100644
index e54a8a5..0000000
--- a/FS/FS/part_pkg/sqlradacct_hour.pm
+++ /dev/null
@@ -1,170 +0,0 @@
-package FS::part_pkg::sqlradacct_hour;
-
-use strict;
-use vars qw(@ISA %info);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'Base charge plus per-hour (and for data) from an SQL RADIUS radacct table',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_flat' => { 'name' => 'Base recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
-
- 'recur_included_hours' => { 'name' => 'Hours included',
- 'default' => 0,
- },
- 'recur_hourly_charge' => { 'name' => 'Additional charge per hour',
- 'default' => 0,
- },
- 'recur_hourly_cap' => { 'name' => 'Maximum overage charge for hours'.
- ' (0 means no cap)',
-
- 'default' => 0,
- },
-
- 'recur_included_input' => { 'name' => 'Upload megabytes included',
- 'default' => 0,
- },
- 'recur_input_charge' => { 'name' =>
- 'Additional charge per megabyte upload',
- 'default' => 0,
- },
- 'recur_input_cap' => { 'name' => 'Maximum overage charge for upload'.
- ' (0 means no cap)',
- 'default' => 0,
- },
-
- 'recur_included_output' => { 'name' => 'Download megabytes included',
- 'default' => 0,
- },
- 'recur_output_charge' => { 'name' =>
- 'Additional charge per megabyte download',
- 'default' => 0,
- },
- 'recur_output_cap' => { 'name' => 'Maximum overage charge for download'.
- ' (0 means no cap)',
- 'default' => 0,
- },
-
- 'recur_included_total' => { 'name' =>
- 'Total megabytes included',
- 'default' => 0,
- },
- 'recur_total_charge' => { 'name' =>
- 'Additional charge per megabyte total',
- 'default' => 0,
- },
- 'recur_total_cap' => { 'name' => 'Maximum overage charge for total'.
- ' megabytes (0 means no cap)',
- 'default' => 0,
- },
-
- 'global_cap' => { 'name' => 'Global cap on all overage charges'.
- ' (0 means no cap)',
- 'default' => 0,
- },
-
- },
- 'fieldorder' => [qw( setup_fee recur_flat unused_credit recur_included_hours recur_hourly_charge recur_hourly_cap recur_included_input recur_input_charge recur_input_cap recur_included_output recur_output_charge recur_output_cap recur_included_total recur_total_charge recur_total_cap global_cap )],
- #'setup' => 'what.setup_fee.value',
- #'recur' => '\'my $last_bill = $cust_pkg->last_bill; my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $sdate ) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; my $input = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctInputOctets\" ) / 1048576; my $output = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctOutputOctets\" ) / 1048576; my $total = $input + $output - \' + what.recur_included_total.value + \'; $total = 0 if $total < 0; my $input = $input - \' + what.recur_included_input.value + \'; $input = 0 if $input < 0; my $output = $output - \' + what.recur_included_output.value + \'; $output = 0 if $output < 0; my $totalcharge = sprintf(\"%.2f\", \' + what.recur_total_charge.value + \' * $total); my $inputcharge = sprintf(\"%.2f\", \' + what.recur_input_charge.value + \' * $input); my $outputcharge = sprintf(\"%.2f\", \' + what.recur_output_charge.value + \' * $output); my $hourscharge = sprintf(\"%.2f\", \' + what.recur_hourly_charge.value + \' * $hours); if ( \' + what.recur_total_charge.value + \' > 0 ) { push @details, \"Last month\\\'s data \". sprintf(\"%.1f\", $total). \" megs: \\\$$totalcharge\" } if ( \' + what.recur_input_charge.value + \' > 0 ) { push @details, \"Last month\\\'s download \". sprintf(\"%.1f\", $input). \" megs: \\\$$inputcharge\" } if ( \' + what.recur_output_charge.value + \' > 0 ) { push @details, \"Last month\\\'s upload \". sprintf(\"%.1f\", $output). \" megs: \\\$$outputcharge\" } if ( \' + what.recur_hourly_charge.value + \' > 0 ) { push @details, \"Last month\\\'s time \". sprintf(\"%.1f\", $hours). \" hours: \\\$$hourscharge\"; } \' + what.recur_flat.value + \' + $hourscharge + $inputcharge + $outputcharge + $totalcharge ;\'',
- 'weight' => 40,
-);
-
-sub calc_recur {
- my($self, $cust_pkg, $sdate, $details ) = @_;
-
- my $last_bill = $cust_pkg->last_bill;
- my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $$sdate ) / 3600;
- $hours -= $self->option('recur_included_hours');
- $hours = 0 if $hours < 0;
-
- my $input = $cust_pkg->attribute_since_sqlradacct( $last_bill,
- $$sdate,
- 'AcctInputOctets' )
- / 1048576;
-
- my $output = $cust_pkg->attribute_since_sqlradacct( $last_bill,
- $$sdate,
- 'AcctOutputOctets' )
- / 1048576;
-
- my $total = $input + $output - $self->option('recur_included_total');
- $total = 0 if $total < 0;
- $input = $input - $self->option('recur_included_input');
- $input = 0 if $input < 0;
- $output = $output - $self->option('recur_included_output');
- $output = 0 if $output < 0;
-
- my $totalcharge =
- $total * sprintf('%.2f', $self->option('recur_total_charge'));
- $totalcharge = $self->option('recur_total_cap')
- if $self->option('recur_total_cap')
- && $totalcharge > $self->option('recur_total_cap');
-
- my $inputcharge =
- $input * sprintf('%.2f', $self->option('recur_input_charge'));
- $inputcharge = $self->option('recur_input_cap')
- if $self->option('recur_input_cap')
- && $inputcharge > $self->option('recur_input_cap');
-
- my $outputcharge =
- $output * sprintf('%.2f', $self->option('recur_output_charge'));
- $outputcharge = $self->option('recur_output_cap')
- if $self->option('recur_output_cap')
- && $outputcharge > $self->option('recur_output_cap');
-
- my $hourscharge =
- $hours * sprintf('%.2f', $self->option('recur_hourly_charge'));
- $hourscharge = $self->option('recur_hours_cap')
- if $self->option('recur_hours_cap')
- && $hourscharge > $self->option('recur_hours_cap');
-
- if ( $self->option('recur_total_charge') > 0 ) {
- push @$details, "Last month's data ".
- sprintf('%.1f', $total). " megs: $totalcharge";
- }
- if ( $self->option('recur_input_charge') > 0 ) {
- push @$details, "Last month's download ".
- sprintf('%.1f', $input). " megs: $inputcharge";
- }
- if ( $self->option('recur_output_charge') > 0 ) {
- push @$details, "Last month's upload ".
- sprintf('%.1f', $output). " megs: $outputcharge";
- }
- if ( $self->option('recur_hourly_charge') > 0 ) {
- push @$details, "Last month\'s time ".
- sprintf('%.1f', $hours). " hours: $hourscharge";
- }
-
- my $charges = $hourscharge + $inputcharge + $outputcharge + $totalcharge;
- if ( $self->option('global_cap') && $charges > $self->option('global_cap') ) {
- $charges = $self->option('global_cap');
- push @$details, "Usage charges capped at: $charges";
- }
-
- $self->option('recur_flat') + $charges;
-}
-
-sub is_free_options {
- qw( setup_fee recur_flat recur_hourly_charge
- recur_input_charge recur_output_charge recur_total_charge );
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- $self->option('recur_flat');
-}
-
-1;
diff --git a/FS/FS/part_pkg/subscription.pm b/FS/FS/part_pkg/subscription.pm
deleted file mode 100644
index c9c472c..0000000
--- a/FS/FS/part_pkg/subscription.pm
+++ /dev/null
@@ -1,108 +0,0 @@
-package FS::part_pkg::subscription;
-
-use strict;
-use vars qw(@ISA %info);
-use Time::Local qw(timelocal);
-#use FS::Record qw(qsearch qsearchs);
-use FS::part_pkg::flat;
-
-@ISA = qw(FS::part_pkg::flat);
-
-%info = (
- 'name' => 'First partial month full charge, then flat-rate (selectable billing day)',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_fee' => { 'name' => 'Recurring fee for this package',
- 'default' => 0,
- },
- 'cutoff_day' => { 'name' => 'billing day',
- 'default' => 1,
- },
- 'seconds' => { 'name' => 'Time limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- },
- 'upbytes' => { 'name' => 'Upload limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'downbytes' => { 'name' => 'Download limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'totalbytes' => { 'name' => 'Transfer limit for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_amount' => { 'name' => 'Cost of recharge for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*(\.\d{2})?$/ },
- },
- 'recharge_seconds' => { 'name' => 'Recharge time for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- },
- 'recharge_upbytes' => { 'name' => 'Recharge upload for this package',
- 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_downbytes' => { 'name' => 'Recharge download for this package', 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', 'default' => '',
- 'check' => sub { shift =~ /^\d*$/ },
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '.
- 'over into current period',
- 'type' => 'checkbox',
- },
- 'recharge_reset' => { 'name' => 'Reset usage to these values on manual '.
- 'package recharge',
- 'type' => 'checkbox',
- },
-
- #it would be better if this had to be turned on, its confusing
- 'externalid' => { 'name' => 'Optional External ID',
- 'default' => '',
- },
- },
- 'fieldorder' => [ 'setup_fee', 'recur_fee', 'cutoff_day', 'seconds',
- 'upbytes', 'downbytes', 'totalbytes',
- 'recharge_amount', 'recharge_seconds', 'recharge_upbytes',
- 'recharge_downbytes', 'recharge_totalbytes',
- 'usage_rollover', 'recharge_reset', 'externalid' ],
- 'freq' => 'm',
- 'weight' => 30,
-);
-
-sub calc_recur {
- my($self, $cust_pkg, $sdate ) = @_;
- my $cutoff_day = $self->option('cutoff_day', 1) || 1;
- my $mnow = $$sdate;
- my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5];
-
- if ( $mday < $cutoff_day ) {
- if ($mon==0) {$mon=11;$year--;}
- else {$mon--;}
- }
-
- $$sdate = timelocal(0,0,0,$cutoff_day,$mon,$year);
-
- $self->option('recur_fee');
-}
-
-1;
diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm
deleted file mode 100644
index ea16031..0000000
--- a/FS/FS/part_pkg/voip_cdr.pm
+++ /dev/null
@@ -1,375 +0,0 @@
-package FS::part_pkg::voip_cdr;
-
-use strict;
-use vars qw(@ISA $DEBUG %info);
-use Date::Format;
-use Tie::IxHash;
-use FS::Conf;
-use FS::Record qw(qsearchs qsearch);
-use FS::part_pkg::flat;
-#use FS::rate;
-#use FS::rate_prefix;
-
-@ISA = qw(FS::part_pkg::flat);
-
-$DEBUG = 1;
-
-tie my %rating_method, 'Tie::IxHash',
- 'prefix' => 'Rate calls by using destination prefix to look up a region and rate according to the internal prefix and rate tables',
- 'upstream' => 'Rate calls based on upstream data: If the call type is "1", map the upstream rate ID directly to an internal rate (rate_detail), otherwise, pass the upstream price through directly.',
-;
-
-#tie my %cdr_location, 'Tie::IxHash',
-# 'internal' => 'Internal: CDR records imported into the internal CDR table',
-# 'external' => 'External: CDR records queried directly from an external '.
-# 'Asterisk (or other?) CDR table',
-#;
-
-%info = (
- 'name' => 'VoIP rating by plan of CDR records in an internal (or external) SQL table',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_flat' => { 'name' => 'Base recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'ratenum' => { 'name' => 'Rate plan',
- 'type' => 'select',
- 'select_table' => 'rate',
- 'select_key' => 'ratenum',
- 'select_label' => 'ratename',
- },
- 'rating_method' => { 'name' => 'Region rating method',
- 'type' => 'radio',
- 'options' => \%rating_method,
- },
-
- 'default_prefix' => { 'name' => 'Default prefix optionally prepended to customer DID numbers when searching for CDR records',
- 'default' => '+1',
- },
-
- 'disable_src' => { 'name' => 'Disable rating of CDR records based on the "src" field in addition to "charged_party"',
- 'type' => 'checkbox'
- },
-
- 'domestic_prefix' => { 'name' => 'Destination prefix for domestic CDR records',
- 'default' => '1',
- },
-
-# 'domestic_prefix_required' => { 'name' => 'Require explicit destination prefix for domestic CDR records',
-# 'type' => 'checkbox',
-# },
-
- 'international_prefix' => { 'name' => 'Destination prefix for international CDR records',
- 'default' => '011',
- },
-
- #XXX also have option for an external db
-# 'cdr_location' => { 'name' => 'CDR database location'
-# 'type' => 'select',
-# 'select_options' => \%cdr_location,
-# 'select_callback' => {
-# 'external' => {
-# 'enable' => [ 'datasrc', 'username', 'password' ],
-# },
-# 'internal' => {
-# 'disable' => [ 'datasrc', 'username', 'password' ],
-# }
-# },
-# },
-# 'datasrc' => { 'name' => 'DBI data source for external CDR table',
-# 'disabled' => 'Y',
-# },
-# 'username' => { 'name' => 'External database username',
-# 'disabled' => 'Y',
-# },
-# 'password' => { 'name' => 'External database password',
-# 'disabled' => 'Y',
-# },
-
- },
- 'fieldorder' => [qw( setup_fee recur_flat unused_credit ratenum rating_method default_prefix disable_src domestic_prefix international_prefix )],
- 'weight' => 40,
-);
-
-sub calc_setup {
- my($self, $cust_pkg ) = @_;
- $self->option('setup_fee');
-}
-
-#false laziness w/voip_sqlradacct... resolve it if that one ever gets used again
-sub calc_recur {
- my($self, $cust_pkg, $sdate, $details, $param ) = @_;
-
- my $last_bill = $cust_pkg->last_bill;
-
- my $ratenum = $cust_pkg->part_pkg->option('ratenum');
-
- my $spool_cdr = $cust_pkg->cust_main->spool_cdr;
-
- my %included_min = ();
-
- my $charges = 0;
-
- my $downstream_cdr = '';
-
- foreach my $cust_svc (
- grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc
- ) {
-
- foreach my $cdr (
- $cust_svc->get_cdrs_for_update() # $last_bill, $$sdate )
- ) {
- if ( $DEBUG > 1 ) {
- warn "rating CDR $cdr\n".
- join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr );
- }
-
- my $rate_detail;
- my( $rate_region, $regionnum );
- my $pretty_destnum;
- my $charge = 0;
- my @call_details = ();
- if ( $self->option('rating_method') eq 'prefix'
- || ! $self->option('rating_method')
- )
- {
-
- ###
- # look up rate details based on called station id
- # (or calling station id for toll free calls)
- ###
-
- my( $to_or_from, $number );
- if ( $cdr->dst =~ /^(\+?1)?8([02-8])\1/ ) { #tollfree call
- $to_or_from = 'from';
- $number = $cdr->src;
- } else { #regular call
- $to_or_from = 'to';
- $number = $cdr->dst;
- }
-
- #remove non-phone# stuff and whitespace
- $number =~ s/\s//g;
-# my $proto = '';
-# $dest =~ s/^(\w+):// and $proto = $1; #sip:
-# my $siphost = '';
-# $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com
-
- my $intl = $self->option('international_prefix') || '011';
-
- #determine the country code
- my $countrycode;
- if ( $number =~ /^$intl(((\d)(\d))(\d))(\d+)$/
- || $number =~ /^\+(((\d)(\d))(\d))(\d+)$/
- )
- {
-
- my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 );
- #first look for 1 digit country code
- if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) {
- $countrycode = $one;
- $number = $u1.$u2.$rest;
- } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2
- $countrycode = $two;
- $number = $u2.$rest;
- } else { #3 digit country code
- $countrycode = $three;
- $number = $rest;
- }
-
- } else {
- $countrycode = $self->option('domestic_prefix') || '1';
- $number =~ s/^$countrycode//;# if length($number) > 10;
- }
-
- warn "rating call $to_or_from +$countrycode $number\n" if $DEBUG;
- $pretty_destnum = "+$countrycode $number";
-
- #find a rate prefix, first look at most specific (4 digits) then 3, etc.,
- # finally trying the country code only
- my $rate_prefix = '';
- for my $len ( reverse(1..6) ) {
- $rate_prefix = qsearchs('rate_prefix', {
- 'countrycode' => $countrycode,
- #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) }
- 'npa' => substr($number, 0, $len),
- } ) and last;
- }
- $rate_prefix ||= qsearchs('rate_prefix', {
- 'countrycode' => $countrycode,
- 'npa' => '',
- });
-
- #
- die "Can't find rate for call $to_or_from +$countrycode $\numbern"
- unless $rate_prefix;
-
- $regionnum = $rate_prefix->regionnum;
- $rate_detail = qsearchs('rate_detail', {
- 'ratenum' => $ratenum,
- 'dest_regionnum' => $regionnum,
- } );
-
- $rate_region = $rate_prefix->rate_region;
-
- warn " found rate for regionnum $regionnum ".
- "and rate detail $rate_detail\n"
- if $DEBUG;
-
- } elsif ( $self->option('rating_method') eq 'upstream' ) {
-
- if ( $cdr->cdrtypenum == 1 ) { #rate based on upstream rateid
-
- $rate_detail = $cdr->cdr_upstream_rate->rate_detail;
-
- $regionnum = $rate_detail->dest_regionnum;
- $rate_region = $rate_detail->dest_region;
-
- $pretty_destnum = $cdr->dst;
-
- warn " found rate for regionnum $regionnum and ".
- "rate detail $rate_detail\n"
- if $DEBUG;
-
- } else { #pass upstream price through
-
- $charge = sprintf('%.2f', $cdr->upstream_price);
-
- @call_details = (
- #time2str("%Y %b %d - %r", $cdr->calldate_unix ),
- time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot
- 'N/A', #minutes...
- '$'.$charge,
- #$pretty_destnum,
- $cdr->description, #$rate_region->regionname,
- );
-
- }
-
- } else {
- die "don't know how to rate CDRs using method: ".
- $self->option('rating_method'). "\n";
- }
-
- ###
- # find the price and add detail to the invoice
- ###
-
- # if $rate_detail is not found, skip this CDR... i.e.
- # don't add it to invoice, don't set its status to NULL,
- # don't call downstream_csv or something on it...
- # but DO emit a warning...
- if ( ! $rate_detail && ! scalar(@call_details) ) {
-
- warn "no rate_detail found for CDR.acctid: ". $cdr->acctid.
- "; skipping\n"
-
- } else { # there *is* a rate_detail (or call_details), proceed...
-
- unless ( @call_details ) {
-
- $included_min{$regionnum} = $rate_detail->min_included
- unless exists $included_min{$regionnum};
-
- my $granularity = $rate_detail->sec_granularity;
- my $seconds = $cdr->billsec; # |ength($cdr->billsec) ? $cdr->billsec : $cdr->duration;
- $seconds += $granularity - ( $seconds % $granularity )
- if $granularity; # 0 is per call
- my $minutes = sprintf("%.1f", $seconds / 60);
- $minutes =~ s/\.0$// if $granularity == 60;
-
- # per call rather than per minute
- $minutes = 1 unless $granularity;
-
- $included_min{$regionnum} -= $minutes;
-
- if ( $included_min{$regionnum} < 0 ) {
- my $charge_min = 0 - $included_min{$regionnum};
- $included_min{$regionnum} = 0;
- $charge = sprintf('%.2f', $rate_detail->min_charge * $charge_min );
- $charges += $charge;
- }
-
- # this is why we need regionnum/rate_region....
- warn " (rate region $rate_region)\n" if $DEBUG;
-
- @call_details = (
- #time2str("%Y %b %d - %r", $cdr->calldate_unix ),
- time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot
- $granularity ? $minutes.'m' : $minutes.' call',
- '$'.$charge,
- $pretty_destnum,
- $rate_region->regionname,
- );
-
- }
-
- warn " adding details on charge to invoice: ".
- join(' - ', @call_details )
- if $DEBUG;
-
- push @$details, join(' - ', @call_details); #\@call_details,
-
- # if the customer flag is on, call "downstream_csv" or something
- # like it to export the call downstream!
- # XXX price plan option to pick format, or something...
- $downstream_cdr .= $cdr->downstream_csv( 'format' => 'convergent' )
- if $spool_cdr;
-
- my $error = $cdr->set_status_and_rated_price('done', $charge);
- die $error if $error;
-
- }
-
- } # $cdr
-
- } # $cust_svc
-
- if ( $spool_cdr && length($downstream_cdr) ) {
-
- use FS::UID qw(datasrc);
- my $dir = '/usr/local/etc/freeside/export.'. datasrc. '/cdr';
- mkdir $dir, 0700 unless -d $dir;
- $dir .= '/'. $cust_pkg->custnum.
- mkdir $dir, 0700 unless -d $dir;
- my $filename = time2str("$dir/CDR%Y%m%d-spool.CSV", time); #XXX invoice date instead? would require changing the order things are generated in cust_main::bill insert cust_bill first - with transactions it could be done though
-
- push @{ $param->{'precommit_hooks'} },
- sub {
- #lock the downstream spool file and append the records
- use Fcntl qw(:flock);
- use IO::File;
- my $spool = new IO::File ">>$filename"
- or die "can't open $filename: $!\n";
- flock( $spool, LOCK_EX)
- or die "can't lock $filename: $!\n";
- seek($spool, 0, 2)
- or die "can't seek to end of $filename: $!\n";
- print $spool $downstream_cdr;
- flock( $spool, LOCK_UN );
- close $spool;
- };
-
- } #if ( $spool_cdr && length($downstream_cdr) )
-
- $self->option('recur_flat') + $charges;
-
-}
-
-sub is_free {
- 0;
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- $self->option('recur_flat');
-}
-
-1;
-
diff --git a/FS/FS/part_pkg/voip_sqlradacct.pm b/FS/FS/part_pkg/voip_sqlradacct.pm
deleted file mode 100644
index bf18003..0000000
--- a/FS/FS/part_pkg/voip_sqlradacct.pm
+++ /dev/null
@@ -1,192 +0,0 @@
-package FS::part_pkg::voip_sqlradacct;
-
-use strict;
-use vars qw(@ISA $DEBUG %info);
-use Date::Format;
-use FS::Record qw(qsearchs qsearch);
-use FS::part_pkg::flat;
-#use FS::rate;
-use FS::rate_prefix;
-
-@ISA = qw(FS::part_pkg::flat);
-
-$DEBUG = 1;
-
-%info = (
- 'name' => 'VoIP rating by plan of CDR records in an SQL RADIUS radacct table',
- 'fields' => {
- 'setup_fee' => { 'name' => 'Setup fee for this package',
- 'default' => 0,
- },
- 'recur_flat' => { 'name' => 'Base recurring fee for this package',
- 'default' => 0,
- },
- 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
- ' of service at cancellation',
- 'type' => 'checkbox',
- },
- 'ratenum' => { 'name' => 'Rate plan',
- 'type' => 'select',
- 'select_table' => 'rate',
- 'select_key' => 'ratenum',
- 'select_label' => 'ratename',
- },
- },
- 'fieldorder' => [qw( setup_fee recur_flat unused_credit ratenum ignore_unrateable )],
- 'weight' => 40,
-);
-
-sub calc_setup {
- my($self, $cust_pkg ) = @_;
- $self->option('setup_fee');
-}
-
-#false laziness w/voip_cdr... resolve it if this one ever gets used again
-sub calc_recur {
- my($self, $cust_pkg, $sdate, $details ) = @_;
-
- my $last_bill = $cust_pkg->last_bill;
-
- my $ratenum = $cust_pkg->part_pkg->option('ratenum');
-
- my %included_min = ();
-
- my $charges = 0;
-
- foreach my $cust_svc (
- grep { $_->part_svc->svcdb eq 'svc_acct' } $cust_pkg->cust_svc
- ) {
-
- foreach my $session (
- $cust_svc->get_session_history( $last_bill, $$sdate )
- ) {
- if ( $DEBUG > 1 ) {
- warn "rating session $session\n".
- join('', map { " $_ => ". $session->{$_}. "\n" } keys %$session );
- }
-
- ###
- # look up rate details based on called station id
- ###
-
- my $dest = $session->{'calledstationid'};
-
- #remove non-phone# stuff and whitespace
- $dest =~ s/\s//g;
- my $proto = '';
- $dest =~ s/^(\w+):// and $proto = $1; #sip:
- my $siphost = '';
- $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com
-
- #determine the country code
- my $countrycode;
- if ( $dest =~ /^011(((\d)(\d))(\d))(\d+)$/ ) {
-
- my( $three, $two, $one, $u1, $u2, $rest ) = ( $1, $2, $3, $4, $5, $6 );
- #first look for 1 digit country code
- if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) {
- $countrycode = $one;
- $dest = $u1.$u2.$rest;
- } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2
- $countrycode = $two;
- $dest = $u2.$rest;
- } else { #3 digit country code
- $countrycode = $three;
- $dest = $rest;
- }
-
- } else {
- $countrycode = '1';
- $dest =~ s/^1//;# if length($dest) > 10;
- }
-
- warn "rating call to +$countrycode $dest\n" if $DEBUG;
-
- #find a rate prefix, first look at most specific (4 digits) then 3, etc.,
- # finally trying the country code only
- my $rate_prefix = '';
- for my $len ( reverse(1..6) ) {
- $rate_prefix = qsearchs('rate_prefix', {
- 'countrycode' => $countrycode,
- #'npa' => { op=> 'LIKE', value=> substr($dest, 0, $len) }
- 'npa' => substr($dest, 0, $len),
- } ) and last;
- }
- $rate_prefix ||= qsearchs('rate_prefix', {
- 'countrycode' => $countrycode,
- 'npa' => '',
- });
-
- die "Can't find rate for call to +$countrycode $dest\n"
- unless $rate_prefix;
-
- my $regionnum = $rate_prefix->regionnum;
- my $rate_detail = qsearchs('rate_detail', {
- 'ratenum' => $ratenum,
- 'dest_regionnum' => $regionnum,
- } );
-
- warn " found rate for regionnum $regionnum ".
- "and rate detail $rate_detail\n"
- if $DEBUG;
-
- ###
- # find the price and add detail to the invoice
- ###
-
- $included_min{$regionnum} = $rate_detail->min_included
- unless exists $included_min{$regionnum};
-
- my $granularity = $rate_detail->sec_granularity;
- my $seconds = $session->{'acctsessiontime'};
- $seconds += $granularity - ( $seconds % $granularity );
- my $minutes = sprintf("%.1f", $seconds / 60);
- $minutes =~ s/\.0$// if $granularity == 60;
-
- $included_min{$regionnum} -= $minutes;
-
- my $charge = 0;
- if ( $included_min{$regionnum} < 0 ) {
- my $charge_min = 0 - $included_min{$regionnum};
- $included_min{$regionnum} = 0;
- $charge = sprintf('%.2f', $rate_detail->min_charge * $charge_min );
- $charges += $charge;
- }
-
- my $rate_region = $rate_prefix->rate_region;
- warn " (rate region $rate_region)\n" if $DEBUG;
-
- my @call_details = (
- #time2str("%Y %b %d - %r", $session->{'acctstarttime'}),
- time2str("%c", $session->{'acctstarttime'}),
- $minutes.'m',
- '$'.$charge,
- "+$countrycode $dest",
- $rate_region->regionname,
- );
-
- warn " adding details on charge to invoice: ".
- join(' - ', @call_details )
- if $DEBUG;
-
- push @$details, join(' - ', @call_details); #\@call_details,
-
- } # $session
-
- } # $cust_svc
-
- $self->option('recur_flat') + $charges;
-
-}
-
-sub is_free {
- 0;
-}
-
-sub base_recur {
- my($self, $cust_pkg) = @_;
- $self->option('recur_flat');
-}
-
-1;
-
diff --git a/FS/FS/part_pkg_option.pm b/FS/FS/part_pkg_option.pm
deleted file mode 100644
index c2f609e..0000000
--- a/FS/FS/part_pkg_option.pm
+++ /dev/null
@@ -1,131 +0,0 @@
-package FS::part_pkg_option;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::part_pkg;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::part_pkg_option - Object methods for part_pkg_option records
-
-=head1 SYNOPSIS
-
- use FS::part_pkg_option;
-
- $record = new FS::part_pkg_option \%hash;
- $record = new FS::part_pkg_option { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_pkg_option object represents an package definition option.
-FS::part_pkg_option inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item optionnum - primary key
-
-=item pkgpart - package definition (see L<FS::part_pkg>)
-
-=item optionname - option name
-
-=item optionvalue - option value
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new package definition option. To add the package definition option
-to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_pkg_option'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid package definition option. If
-there is an error, returns the error, otherwise returns false. Called by the
-insert and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('optionnum')
- || $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart')
- || $self->ut_alpha('optionname')
- || $self->ut_anything('optionvalue')
- ;
- return $error if $error;
-
- #check options & values?
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-Possibly.
-
-=head1 SEE ALSO
-
-L<FS::part_pkg>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_pkg_taxclass.pm b/FS/FS/part_pkg_taxclass.pm
deleted file mode 100644
index fda200e..0000000
--- a/FS/FS/part_pkg_taxclass.pm
+++ /dev/null
@@ -1,158 +0,0 @@
-package FS::part_pkg_taxclass;
-
-use strict;
-use vars qw( @ISA );
-use FS::UID qw(dbh);
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::part_pkg_taxclass - Object methods for part_pkg_taxclass records
-
-=head1 SYNOPSIS
-
- use FS::part_pkg_taxclass;
-
- $record = new FS::part_pkg_taxclass \%hash;
- $record = new FS::part_pkg_taxclass { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_pkg_taxclass object represents a tax class. FS::part_pkg_taxclass
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item taxclassnum
-
-Primary key
-
-=item taxclass
-
-Tax class
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new tax class. To add the tax class to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'part_pkg_taxclass'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid tax class. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('taxclassnum')
- || $self->ut_text('taxclass')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=cut
-
-# _upgrade_data
-#
-# Used by FS::Upgrade to migrate to a new database.
-
-sub _upgrade_data { # class method
- my ($class, %opts) = @_;
-
- my $sth = dbh->prepare('
- SELECT DISTINCT taxclass
- FROM cust_main_county
- LEFT JOIN part_pkg_taxclass USING ( taxclass )
- WHERE taxclassnum IS NULL
- AND taxclass IS NOT NULL
- ') or die dbh->errstr;
- $sth->execute or die $sth->errstr;
- my %taxclass = map { $_->[0] => 1 } @{$sth->fetchall_arrayref};
- my @taxclass = grep $_, keys %taxclass;
-
- foreach my $taxclass ( @taxclass ) {
-
- my $part_pkg_taxclass = new FS::part_pkg_taxclass ( {
- 'taxclass' => $taxclass,
- } );
- my $error = $part_pkg_taxclass->insert;
- die $error if $error;
-
- }
-
-}
-
-=head1 BUGS
-
-Other tables (cust_main_county, part_pkg, agent_payment_gateway) have a text
-taxclass instead of a key to this table.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_pop_local.pm b/FS/FS/part_pop_local.pm
deleted file mode 100644
index 01c59df..0000000
--- a/FS/FS/part_pop_local.pm
+++ /dev/null
@@ -1,113 +0,0 @@
-package FS::part_pop_local;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record; # qw( qsearchs );
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::part_pop_local - Object methods for part_pop_local records
-
-=head1 SYNOPSIS
-
- use FS::part_pop_local;
-
- $record = new FS::part_pop_local \%hash;
- $record = new FS::part_pop_local { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_pop_local object represents a local call area. Each
-FS::part_pop_local record maps a NPA/NXX (area code and exchange) to the POP
-(see L<FS::svc_acct_pop>) which is a local call. FS::part_pop_local inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item localnum - primary key (assigned automatically for new accounts)
-
-=item popnum - see L<FS::svc_acct_pop>
-
-=item city
-
-=item state
-
-=item npa - area code
-
-=item nxx - exchange
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new point of presence (if only it were that easy!). To add the
-point of presence to the database, see L<"insert">.
-
-=cut
-
-sub table { 'part_pop_local'; }
-
-=item insert
-
-Adds this point of presence to the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item delete
-
-Removes this point of presence from the database.
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid point of presence. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('localnum')
- or $self->ut_numbern('popnum')
- or $self->ut_text('city')
- or $self->ut_text('state')
- or $self->ut_number('npa')
- or $self->ut_number('nxx')
- or $self->SUPER::check
- ;
-
-}
-
-=back
-
-=head1 BUGS
-
-US/CA-centric.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::svc_acct_pop>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_referral.pm b/FS/FS/part_referral.pm
deleted file mode 100644
index 87bc87c..0000000
--- a/FS/FS/part_referral.pm
+++ /dev/null
@@ -1,204 +0,0 @@
-package FS::part_referral;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs dbh );
-use FS::agent;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::part_referral - Object methods for part_referral objects
-
-=head1 SYNOPSIS
-
- use FS::part_referral;
-
- $record = new FS::part_referral \%hash
- $record = new FS::part_referral { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_referral represents a advertising source - where a customer heard
-of your services. This can be used to track the effectiveness of a particular
-piece of advertising, for example. FS::part_referral inherits from FS::Record.
-The following fields are currently supported:
-
-=over 4
-
-=item refnum - primary key (assigned automatically for new referrals)
-
-=item referral - Text name of this advertising source
-
-=item disabled - Disabled flag, empty or 'Y'
-
-=item agentnum - Optional agentnum (see L<FS::agent>)
-
-=back
-
-=head1 NOTE
-
-These were called B<referrals> before version 1.4.0 - the name was changed
-so as not to be confused with the new customer-to-customer referrals.
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new advertising source. To add the referral to the database, see
-L<"insert">.
-
-=cut
-
-sub table { 'part_referral'; }
-
-=item insert
-
-Adds this advertising source to the database. If there is an error, returns
-the error, otherwise returns false.
-
-=item delete
-
-Currently unimplemented.
-
-=cut
-
-sub delete {
- my $self = shift;
- return "Can't (yet?) delete part_referral records";
- #need to make sure no customers have this referral!
-}
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid advertising source. If there is
-an error, returns the error, otherwise returns false. Called by the insert and
-replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error = $self->ut_numbern('refnum')
- || $self->ut_text('referral')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
- || $self->ut_agentnum_acl('agentnum', 'Edit global advertising sources')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item agent
-
-Returns the associated agent for this referral, if any, as an FS::agent object.
-
-=cut
-
-sub agent {
- my $self = shift;
- qsearchs('agent', { 'agentnum' => $self->agentnum } );
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=item acl_agentnum_sql [ INCLUDE_GLOBAL_BOOL ]
-
-Returns an SQL fragment for searching for part_referral records allowed by the
-current users's agent ACLs (and "Edit global advertising sources" right).
-
-Pass a true value to include global advertising sources (for example, when
-simply using rather than editing advertising sources).
-
-=cut
-
-sub acl_agentnum_sql {
- my $self = shift;
-
- my $curuser = $FS::CurrentUser::CurrentUser;
- my $sql = $curuser->agentnums_sql;
- $sql = " ( $sql OR agentnum IS NULL ) "
- if $curuser->access_right('Edit global advertising sources')
- or defined($_[0]) && $_[0];
-
- $sql;
-
-}
-
-=item all_part_referral [ INCLUDE_GLOBAL_BOOL ]
-
-Returns all part_referral records allowed by the current users's agent ACLs
-(and "Edit global advertising sources" right).
-
-Pass a true value to include global advertising sources (for example, when
-simply using rather than editing advertising sources).
-
-=cut
-
-sub all_part_referral {
- my $self = shift;
-
- qsearch({
- 'table' => 'part_referral',
- 'extra_sql' => ' WHERE '. $self->acl_agentnum_sql(@_). ' ORDER BY refnum ',
- });
-
-}
-
-=item num_part_referral [ INCLUDE_GLOBAL_BOOL ]
-
-Returns the number of part_referral records allowed by the current users's
-agent ACLs (and "Edit global advertising sources" right).
-
-=cut
-
-sub num_part_referral {
- my $self = shift;
-
- my $sth = dbh->prepare(
- 'SELECT COUNT(*) FROM part_referral WHERE '. $self->acl_agentnum_sql(@_)
- ) or die dbh->errstr;
- $sth->execute() or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
-
-=back
-
-=head1 BUGS
-
-The delete method is unimplemented.
-
-`Advertising source'. Yes, it's a sucky name. The only other ones I could
-come up with were "Marketing channel" and "Heard Abouts" and those are
-definately both worse.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_main>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
deleted file mode 100644
index 4fae457..0000000
--- a/FS/FS/part_svc.pm
+++ /dev/null
@@ -1,825 +0,0 @@
-package FS::part_svc;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use Tie::IxHash;
-use FS::Record qw( qsearch qsearchs fields dbh );
-use FS::Schema qw( dbdef );
-use FS::part_svc_column;
-use FS::part_export;
-use FS::export_svc;
-use FS::cust_svc;
-
-@ISA = qw(FS::Record);
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::part_svc - Object methods for part_svc objects
-
-=head1 SYNOPSIS
-
- use FS::part_svc;
-
- $record = new FS::part_svc \%hash
- $record = new FS::part_svc { 'column' => 'value' };
-
- $error = $record->insert;
- $error = $record->insert( [ 'pseudofield' ] );
- $error = $record->insert( [ 'pseudofield' ], \%exportnums );
-
- $error = $new_record->replace($old_record);
- $error = $new_record->replace($old_record, '1.3-COMPAT', [ 'pseudofield' ] );
- $error = $new_record->replace($old_record, '1.3-COMPAT', [ 'pseudofield' ], \%exportnums );
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_svc represents a service definition. FS::part_svc inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item svcpart - primary key (assigned automatically for new service definitions)
-
-=item svc - text name of this service definition
-
-=item svcdb - table used for this service. See L<FS::svc_acct>,
-L<FS::svc_domain>, and L<FS::svc_forward>, among others.
-
-=item disabled - Disabled flag, empty or `Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new service definition. To add the service definition to the
-database, see L<"insert">.
-
-=cut
-
-sub table { 'part_svc'; }
-
-=item insert [ EXTRA_FIELDS_ARRAYREF [ , EXPORTNUMS_HASHREF [ , JOB ] ] ]
-
-Adds this service definition to the database. If there is an error, returns
-the error, otherwise returns false.
-
-The following pseudo-fields may be defined, and will be maintained in
-the part_svc_column table appropriately (see L<FS::part_svc_column>).
-
-=over 4
-
-=item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
-
-=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null or empty (no default), `D' for default, `F' for fixed (unchangeable), `M' for manual selection from inventory, or `A' for automatic selection from inventory. For virtual fields, can also be 'X' for excluded.
-
-=back
-
-If you want to add part_svc_column records for fields that do not exist as
-(real or virtual) fields in the I<svcdb> table, make sure to list then in
-EXTRA_FIELDS_ARRAYREF also.
-
-If EXPORTNUMS_HASHREF is specified (keys are exportnums and values are
-boolean), the appopriate export_svc records will be inserted.
-
-TODOC: JOB
-
-=cut
-
-sub insert {
- my $self = shift;
- my @fields = ();
- my @exportnums = ();
- @fields = @{shift(@_)} if @_;
- if ( @_ ) {
- my $exportnums = shift;
- @exportnums = grep $exportnums->{$_}, keys %$exportnums;
- }
- my $job = '';
- $job = shift if @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- # add part_svc_column records
-
- my $svcdb = $self->svcdb;
-# my @rows = map { /^${svcdb}__(.*)$/; $1 }
-# grep ! /_flag$/,
-# grep /^${svcdb}__/,
-# fields('part_svc');
- foreach my $field (
- grep { $_ ne 'svcnum'
- && defined( $self->getfield($svcdb.'__'.$_.'_flag') )
- } (fields($svcdb), @fields)
- ) {
- my $part_svc_column = $self->part_svc_column($field);
- my $previous = qsearchs('part_svc_column', {
- 'svcpart' => $self->svcpart,
- 'columnname' => $field,
- } );
-
- my $flag = $self->getfield($svcdb.'__'.$field.'_flag');
- #if ( uc($flag) =~ /^([DFMAX])$/ ) {
- if ( uc($flag) =~ /^([A-Z])$/ ) { #part_svc_column will test it
- my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse}
- || sub { shift };
- $part_svc_column->setfield('columnflag', $1);
- $part_svc_column->setfield('columnvalue',
- &$parser($self->getfield($svcdb.'__'.$field))
- );
- if ( $previous ) {
- $error = $part_svc_column->replace($previous);
- } else {
- $error = $part_svc_column->insert;
- }
- } else {
- $error = $previous ? $previous->delete : '';
- }
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- # add export_svc records
- my $slice = 100/scalar(@exportnums) if @exportnums;
- my $done = 0;
- foreach my $exportnum ( @exportnums ) {
- my $export_svc = new FS::export_svc ( {
- 'exportnum' => $exportnum,
- 'svcpart' => $self->svcpart,
- } );
- $error = $export_svc->insert($job, $slice*$done++, $slice);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-=item delete
-
-Currently unimplemented. Set the "disabled" field instead.
-
-=cut
-
-sub delete {
- return "Can't (yet?) delete service definitions.";
-# check & make sure the svcpart isn't in cust_svc or pkg_svc (in any packages)?
-}
-
-=item replace OLD_RECORD [ '1.3-COMPAT' [ , EXTRA_FIELDS_ARRAYREF [ , EXPORTNUMS_HASHREF [ , JOB ] ] ] ]
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-TODOC: 1.3-COMPAT
-
-TODOC: EXTRA_FIELDS_ARRAYREF (same as insert method)
-
-TODOC: JOB
-
-=cut
-
-sub replace {
- my ( $new, $old ) = ( shift, shift );
- my $compat = '';
- my @fields = ();
- my $exportnums;
- my $job = '';
- if ( @_ && $_[0] eq '1.3-COMPAT' ) {
- shift;
- $compat = '1.3';
- @fields = @{shift(@_)} if @_;
- $exportnums = @_ ? shift : '';
- $job = shift if @_;
- } else {
- return 'non-1.3-COMPAT interface not yet written';
- #not yet implemented
- }
-
- return "Can't change svcdb for an existing service definition!"
- unless $old->svcdb eq $new->svcdb;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $new->SUPER::replace( $old );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $compat eq '1.3' ) {
-
- # maintain part_svc_column records
-
- my $svcdb = $new->svcdb;
- foreach my $field (
- grep { $_ ne 'svcnum'
- && defined( $new->getfield($svcdb.'__'.$_.'_flag') )
- } (fields($svcdb),@fields)
- ) {
- my $part_svc_column = $new->part_svc_column($field);
- my $previous = qsearchs('part_svc_column', {
- 'svcpart' => $new->svcpart,
- 'columnname' => $field,
- } );
-
- my $flag = $new->getfield($svcdb.'__'.$field.'_flag');
- #if ( uc($flag) =~ /^([DFMAX])$/ ) {
- if ( uc($flag) =~ /^([A-Z])$/ ) { #part_svc_column will test it
- my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse}
- || sub { shift };
- $part_svc_column->setfield('columnflag', $1);
- $part_svc_column->setfield('columnvalue',
- &$parser($new->getfield($svcdb.'__'.$field))
- );
- if ( $previous ) {
- $error = $part_svc_column->replace($previous);
- } else {
- $error = $part_svc_column->insert;
- }
- } else {
- $error = $previous ? $previous->delete : '';
- }
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- # maintain export_svc records
-
- if ( $exportnums ) {
-
- #false laziness w/ edit/process/agent_type.cgi
- my @new_export_svc = ();
- foreach my $part_export ( qsearch('part_export', {}) ) {
- my $exportnum = $part_export->exportnum;
- my $hashref = {
- 'exportnum' => $exportnum,
- 'svcpart' => $new->svcpart,
- };
- my $export_svc = qsearchs('export_svc', $hashref);
-
- if ( $export_svc && ! $exportnums->{$exportnum} ) {
- $error = $export_svc->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- } elsif ( ! $export_svc && $exportnums->{$exportnum} ) {
- push @new_export_svc, new FS::export_svc ( $hashref );
- }
-
- }
-
- my $slice = 100/scalar(@new_export_svc) if @new_export_svc;
- my $done = 0;
- foreach my $export_svc (@new_export_svc) {
- $error = $export_svc->insert($job, $slice*$done++, $slice);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- if ( $job ) {
- $error = $job->update_statustext( int( $slice * $done ) );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- }
-
- } else {
- $dbh->rollback if $oldAutoCommit;
- return 'non-1.3-COMPAT interface not yet written';
- #not yet implemented
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-=item check
-
-Checks all fields to make sure this is a valid service definition. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error;
- $error=
- $self->ut_numbern('svcpart')
- || $self->ut_text('svc')
- || $self->ut_alpha('svcdb')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- ;
- return $error if $error;
-
- my @fields = eval { fields( $self->svcdb ) }; #might die
- return "Unknown svcdb: ". $self->svcdb. " (Error: $@)"
- unless @fields;
-
- $self->SUPER::check;
-}
-
-=item part_svc_column COLUMNNAME
-
-Returns the part_svc_column object (see L<FS::part_svc_column>) for the given
-COLUMNNAME, or a new part_svc_column object if none exists.
-
-=cut
-
-sub part_svc_column {
- my( $self, $columnname) = @_;
- $self->svcpart &&
- qsearchs('part_svc_column', {
- 'svcpart' => $self->svcpart,
- 'columnname' => $columnname,
- }
- ) or new FS::part_svc_column {
- 'svcpart' => $self->svcpart,
- 'columnname' => $columnname,
- };
-}
-
-=item all_part_svc_column
-
-=cut
-
-sub all_part_svc_column {
- my $self = shift;
- qsearch('part_svc_column', { 'svcpart' => $self->svcpart } );
-}
-
-=item part_export [ EXPORTTYPE ]
-
-Returns a list of all exports (see L<FS::part_export>) for this service, or,
-if an export type is specified, only returns exports of the given type.
-
-=cut
-
-sub part_export {
- my $self = shift;
- my %search;
- $search{'exporttype'} = shift if @_;
- map { qsearchs('part_export', { 'exportnum' => $_->exportnum, %search } ) }
- qsearch('export_svc', { 'svcpart' => $self->svcpart } );
-}
-
-=item part_export_usage
-
-Returns a list of any exports (see L<FS::part_export>) for this service that
-are capable of reporting usage information.
-
-=cut
-
-sub part_export_usage {
- my $self = shift;
- grep $_->can('usage_sessions'), $self->part_export;
-}
-
-=item cust_svc [ PKGPART ]
-
-Returns a list of associated customer services (FS::cust_svc records).
-
-If a PKGPART is specified, returns the customer services which are contained
-within packages of that type (see L<FS::part_pkg>). If PKGPARTis specified as
-B<0>, returns unlinked customer services.
-
-=cut
-
-sub cust_svc {
- my $self = shift;
-
- my $hashref = { 'svcpart' => $self->svcpart };
-
- my( $addl_from, $extra_sql ) = ( '', '' );
- if ( @_ ) {
- my $pkgpart = shift;
- if ( $pkgpart =~ /^(\d+)$/ ) {
- $addl_from = 'LEFT JOIN cust_pkg USING ( pkgnum )';
- $extra_sql = "AND pkgpart = $1";
- } elsif ( $pkgpart eq '0' ) {
- $hashref->{'pkgnum'} = '';
- }
- }
-
- qsearch({
- 'table' => 'cust_svc',
- 'addl_from' => $addl_from,
- 'hashref' => $hashref,
- 'extra_sql' => $extra_sql,
- });
-}
-
-=item num_cust_svc [ PKGPART ]
-
-Returns the number of associated customer services (FS::cust_svc records).
-
-If a PKGPART is specified, returns the number of customer services which are
-contained within packages of that type (see L<FS::part_pkg>). If PKGPART
-is specified as B<0>, returns the number of unlinked customer services.
-
-=cut
-
-sub num_cust_svc {
- my $self = shift;
-
- my @param = ( $self->svcpart );
-
- my( $join, $and ) = ( '', '' );
- if ( @_ ) {
- my $pkgpart = shift;
- if ( $pkgpart ) {
- $join = 'LEFT JOIN cust_pkg USING ( pkgnum )';
- $and = 'AND pkgpart = ?';
- push @param, $pkgpart;
- } elsif ( $pkgpart eq '0' ) {
- $and = 'AND pkgnum IS NULL';
- }
- }
-
- my $sth = dbh->prepare(
- "SELECT COUNT(*) FROM cust_svc $join WHERE svcpart = ? $and"
- ) or die dbh->errstr;
- $sth->execute(@param)
- or die $sth->errstr;
- $sth->fetchrow_arrayref->[0];
-}
-
-=item svc_x
-
-Returns a list of associated FS::svc_* records.
-
-=cut
-
-sub svc_x {
- my $self = shift;
- map { $_->svc_x } $self->cust_svc;
-}
-
-=back
-
-=head1 CLASS METHODS
-
-=over 4
-
-=cut
-
-my $svc_defs;
-sub _svc_defs {
-
- return $svc_defs if $svc_defs; #cache
-
- my $conf = new FS::Conf;
-
- #false laziness w/part_pkg.pm::plan_info
-
- my %info;
- foreach my $INC ( @INC ) {
- warn "globbing $INC/FS/svc_*.pm\n" if $DEBUG;
- foreach my $file ( glob("$INC/FS/svc_*.pm") ) {
-
- warn "attempting to load service table info from $file\n" if $DEBUG;
- $file =~ /\/(\w+)\.pm$/ or do {
- warn "unrecognized file in $INC/FS/: $file\n";
- next;
- };
- my $mod = $1;
-
- if ( $mod =~ /^svc_[A-Z]/ or $mod =~ /^svc_acct_pop$/ ) {
- warn "skipping FS::$mod" if $DEBUG;
- next;
- }
-
- eval "use FS::$mod;";
- if ( $@ ) {
- die "error using FS::$mod (skipping): $@\n" if $@;
- next;
- }
- unless ( UNIVERSAL::can("FS::$mod", 'table_info') ) {
- warn "FS::$mod has no table_info method; skipping";
- next;
- }
-
- my $info = "FS::$mod"->table_info;
- unless ( keys %$info ) {
- warn "FS::$mod->table_info doesn't return info, skipping\n";
- next;
- }
- warn "got info from FS::$mod: $info\n" if $DEBUG;
- if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
- warn "skipping disabled service FS::$mod" if $DEBUG;
- next;
- }
- $info{$mod} = $info;
- }
- }
-
- tie my %svc_defs, 'Tie::IxHash',
- map { $_ => $info{$_}->{'fields'} }
- sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} }
- keys %info,
- ;
-
- # yuck. maybe this won't be so bad when virtual fields become real fields
- my %vfields;
- foreach my $svcdb (grep dbdef->table($_), keys %svc_defs ) {
- eval "use FS::$svcdb;";
- my $self = "FS::$svcdb"->new;
- $vfields{$svcdb} = {};
- foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them
- my $pvf = $self->pvf($field);
- my @list = $pvf->list;
- if (scalar @list) {
- $svc_defs{$svcdb}->{$field} = { desc => $pvf->label,
- type => 'select',
- select_list => \@list };
- } else {
- $svc_defs{$svcdb}->{$field} = $pvf->label;
- } #endif
- $vfields{$svcdb}->{$field} = $pvf;
- warn "\$vfields{$svcdb}->{$field} = $pvf"
- if $DEBUG;
- } #next $field
- } #next $svcdb
-
- $svc_defs = \%svc_defs; #cache
-
-}
-
-=item svc_tables
-
-Returns a list of all svc_ tables.
-
-=cut
-
-sub svc_tables {
- my $class = shift;
- my $svc_defs = $class->_svc_defs;
- grep { defined( dbdef->table($_) ) } keys %$svc_defs;
-}
-
-=item svc_table_fields TABLE
-
-Given a table name, returns a hashref of field names. The field names
-returned are those with additional (service-definition related) information,
-not necessarily all database fields of the table. Pseudo-fields may also
-be returned (i.e. svc_acct.usergroup).
-
-Each value of the hashref is another hashref, which can have one or more of
-the following keys:
-
-=over 4
-
-=item label - Description of the field
-
-=item def_label - Optional description of the field in the context of service definitions
-
-=item type - Currently "text", "select", "disabled", or "radius_usergroup_selector"
-
-=item disable_default - This field should not allow a default value in service definitions
-
-=item disable_fixed - This field should not allow a fixed value in service definitions
-
-=item disable_inventory - This field should not allow inventory values in service definitions
-
-=item select_list - If type is "text", this can be a listref of possible values.
-
-=item select_table - An alternative to select_list, this defines a database table with the possible choices.
-
-=item select_key - Used with select_table, this is the field name of keys
-
-=item select_label - Used with select_table, this is the field name of labels
-
-=back
-
-=cut
-
-#maybe this should move and be a class method in svc_Common.pm
-sub svc_table_fields {
- my($class, $table) = @_;
- my $svc_defs = $class->_svc_defs;
- my $def = $svc_defs->{$table};
-
- foreach ( grep !ref($def->{$_}), keys %$def ) {
-
- #normalize the shortcut in %info hash
- $def->{$_} = { 'label' => $def->{$_} };
-
- $def->{$_}{'type'} ||= 'text';
-
- }
-
- $def;
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item process
-
-Job-queue processor for web interface adds/edits
-
-=cut
-
-use Storable qw(thaw);
-use Data::Dumper;
-use MIME::Base64;
-sub process {
- my $job = shift;
-
- my $param = thaw(decode_base64(shift));
- warn Dumper($param) if $DEBUG;
-
- my $old = qsearchs('part_svc', { 'svcpart' => $param->{'svcpart'} })
- if $param->{'svcpart'};
-
- $param->{'svc_acct__usergroup'} =
- ref($param->{'svc_acct__usergroup'})
- ? join(',', @{$param->{'svc_acct__usergroup'}} )
- : $param->{'svc_acct__usergroup'};
-
- my $new = new FS::part_svc ( {
- map {
- $_ => $param->{$_};
- # } qw(svcpart svc svcdb)
- } ( fields('part_svc'),
- map { my $svcdb = $_;
- my @fields = fields($svcdb);
- push @fields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
-
- map {
- if ( $param->{ $svcdb.'__'.$_.'_flag' } =~ /^[MA]$/ ) {
- $param->{ $svcdb.'__'.$_ } =
- delete( $param->{ $svcdb.'__'.$_.'_classnum' } );
- }
- if ( $param->{ $svcdb.'__'.$_.'_flag' } =~ /^S$/ ) {
- $param->{ $svcdb.'__'.$_} =
- ref($param->{ $svcdb.'__'.$_})
- ? join(',', @{$param->{ $svcdb.'__'.$_ }} )
- : $param->{ $svcdb.'__'.$_ };
- }
- ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' );
- }
- @fields;
-
- } FS::part_svc->svc_tables()
- )
- } );
-
- my %exportnums =
- map { $_->exportnum => ( $param->{'exportnum'.$_->exportnum} || '') }
- qsearch('part_export', {} );
-
- my $error;
- if ( $param->{'svcpart'} ) {
- $error = $new->replace( $old,
- '1.3-COMPAT',
- [ 'usergroup' ],
- \%exportnums,
- $job
- );
- } else {
- $error = $new->insert( [ 'usergroup' ],
- \%exportnums,
- $job,
- );
- $param->{'svcpart'} = $new->getfield('svcpart');
- }
-
- die "$error\n" if $error;
-}
-
-=item process_bulk_cust_svc
-
-Job-queue processor for web interface bulk customer service changes
-
-=cut
-
-use Storable qw(thaw);
-use Data::Dumper;
-use MIME::Base64;
-sub process_bulk_cust_svc {
- my $job = shift;
-
- my $param = thaw(decode_base64(shift));
- warn Dumper($param) if $DEBUG;
-
- my $old_part_svc =
- qsearchs('part_svc', { 'svcpart' => $param->{'old_svcpart'} } );
-
- die "Must select a new service definition\n" unless $param->{'new_svcpart'};
-
- #the rest should be abstracted out to to its own subroutine?
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- local( $FS::cust_svc::ignore_quantity ) = 1;
-
- my $total = $old_part_svc->num_cust_svc( $param->{'pkgpart'} );
-
- my $n = 0;
- foreach my $old_cust_svc ( $old_part_svc->cust_svc( $param->{'pkgpart'} ) ) {
-
- my $new_cust_svc = new FS::cust_svc { $old_cust_svc->hash };
-
- $new_cust_svc->svcpart( $param->{'new_svcpart'} );
- my $error = $new_cust_svc->replace($old_cust_svc);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- die "$error\n" if $error;
- }
-
- $error = $job->update_statustext( int( 100 * ++$n / $total ) );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- die $error if $error;
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=head1 BUGS
-
-Delete is unimplemented.
-
-The list of svc_* tables is no longer hardcoded, but svc_acct_pop is skipped
-as a special case until it is renamed.
-
-all_part_svc_column methods should be documented
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::part_svc_column>, L<FS::part_pkg>, L<FS::pkg_svc>,
-L<FS::cust_svc>, L<FS::svc_acct>, L<FS::svc_forward>, L<FS::svc_domain>,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm
deleted file mode 100644
index d2b8fd9..0000000
--- a/FS/FS/part_svc_column.pm
+++ /dev/null
@@ -1,120 +0,0 @@
-package FS::part_svc_column;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( fields );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::part_svc_column - Object methods for part_svc_column objects
-
-=head1 SYNOPSIS
-
- use FS::part_svc_column;
-
- $record = new FS::part_svc_column \%hash
- $record = new FS::part_svc_column { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_svc_column record represents a service definition column
-constraint. FS::part_svc_column inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item columnnum - primary key (assigned automatcially for new records)
-
-=item svcpart - service definition (see L<FS::part_svc>)
-
-=item columnname - column name in part_svc.svcdb table
-
-=item columnvalue - default or fixed value for the column
-
-=item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, or `A' for automatic selection from inventory. For virtual fields, can also be 'X' for excluded.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new column constraint. To add the column constraint to the database, see L<"insert">.
-
-=cut
-
-sub table { 'part_svc_column'; }
-
-=item insert
-
-Adds this service definition to the database. If there is an error, returns
-the error, otherwise returns false.
-
-=item delete
-
-Deletes this record from the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('columnnum')
- || $self->ut_number('svcpart')
- || $self->ut_alpha('columnname')
- || $self->ut_anything('columnvalue')
- ;
- return $error if $error;
-
- $self->columnflag =~ /^([DFSMAX])$/
- or return "illegal columnflag ". $self->columnflag;
- $self->columnflag(uc($1));
-
- if ( $self->columnflag =~ /^[MA]$/ ) {
- $error =
- $self->ut_foreign_key( 'columnvalue', 'inventory_class', 'classnum' );
- return $error if $error;
- }
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::part_svc>, L<FS::part_pkg>, L<FS::pkg_svc>,
-L<FS::cust_svc>, L<FS::svc_acct>, L<FS::svc_forward>, L<FS::svc_domain>,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/part_svc_router.pm b/FS/FS/part_svc_router.pm
deleted file mode 100755
index df04cc9..0000000
--- a/FS/FS/part_svc_router.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::part_svc_router;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw(qsearchs);
-use FS::router;
-use FS::part_svc;
-
-@ISA = qw(FS::Record);
-
-sub table { 'part_svc_router'; }
-
-sub check {
- my $self = shift;
- my $error =
- $self->ut_numbern('svcrouternum')
- || $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart')
- || $self->ut_foreign_key('routernum', 'router', 'routernum');
- return $error if $error;
- ''; #no error
-}
-
-sub router {
- my $self = shift;
- return qsearchs('router', { routernum => $self->routernum });
-}
-
-sub part_svc {
- my $self = shift;
- return qsearchs('part_svc', { svcpart => $self->svcpart });
-}
-
-1;
diff --git a/FS/FS/part_virtual_field.pm b/FS/FS/part_virtual_field.pm
deleted file mode 100755
index ea973ba..0000000
--- a/FS/FS/part_virtual_field.pm
+++ /dev/null
@@ -1,301 +0,0 @@
-package FS::part_virtual_field;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearchs qsearch );
-use FS::Schema qw( dbdef );
-use CGI qw(escapeHTML);
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::part_virtual_field - Object methods for part_virtual_field records
-
-=head1 SYNOPSIS
-
- use FS::part_virtual_field;
-
- $record = new FS::part_virtual_field \%hash;
- $record = new FS::part_virtual_field { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::part_virtual_field object represents the definition of a virtual field
-(see the BACKGROUND section). FS::part_virtual_field contains the name and
-base table of the field, as well as validation rules and UI hints about the
-display of the field. The actual data is stored in FS::virtual_field; see
-its manpage for details.
-
-FS::part_virtual_field inherits from FS::Record. The following fields are
-currently supported:
-
-=over 2
-
-=item vfieldpart - primary key (assigned automatically)
-
-=item name - name of the field
-
-=item dbtable - table for which this virtual field is defined
-
-=item check_block - Perl code to validate/normalize data
-
-=item list_source - Perl code to generate a list of values (UI hint)
-
-=item length - expected length of the value (UI hint)
-
-=item label - descriptive label for the field (UI hint)
-
-=item sequence - sort key (UI hint; unimplemented)
-
-=back
-
-=head1 BACKGROUND
-
-"Form is none other than emptiness,
- and emptiness is none other than form."
--- Heart Sutra
-
-The virtual field mechanism allows site admins to make trivial changes to
-the Freeside database schema without modifying the code. Specifically, the
-user can add custom-defined 'fields' to the set of data tracked by Freeside
-about objects such as customers and services. These fields are not associated
-with any logic in the core Freeside system, but may be referenced in peripheral
-code such as exports, price calculations, or alternate interfaces, or may just
-be stored in the database for future reference.
-
-This system was originally devised for svc_broadband, which (by necessity)
-comprises such a wide range of access technologies that no static set of fields
-could contain all the information needed by the exports. In an appalling
-display of False Laziness, a parallel mechanism was implemented for the
-router table, to store properties such as passwords to configure routers.
-
-The original system treated svc_broadband custom fields (sb_fields) as records
-in a completely separate table. Any code that accessed or manipulated these
-fields had to be aware that they were I<not> fields in svc_broadband, but
-records in sb_field. For example, code that inserted a svc_broadband with
-several custom fields had to create an FS::svc_broadband object, call its
-insert() method, and then create several FS::sb_field objects and call I<their>
-insert() methods.
-
-This created a problem for exports. The insert method on any FS::svc_Common
-object (including svc_broadband) automatically triggers exports after the
-record has been inserted. However, at this point, the sb_fields had not yet
-been inserted, so the export could not rely on their presence, which was the
-original purpose of sb_fields.
-
-Hence the new system. Virtual fields are appended to the field list of every
-record at the FS::Record level, whether the object is created ex nihilo with
-new() or fetched with qsearch(). The fields() method now returns a list of
-both real and virtual fields. The insert(), replace(), and delete() methods
-now update both the base table and the virtual fields, in a single transaction.
-
-A new method is provided, virtual_fields(), which gives only the virtual
-fields. UI code that dynamically generates form widgets to edit virtual field
-data should use this to figure out what fields are defined. (See below.)
-
-Subclasses may override virtual_fields() to restrict the set of virtual
-fields available. Some discipline and sanity on the part of the programmer
-are required; in particular, this function should probably not depend on any
-fields in the record other than the primary key, since the others may change
-after the object is instantiated. (Making it depend on I<virtual> fields is
-just asking for pain.) One use of this is seen in FS::svc_Common; another
-possibility is field-level access control based on FS::UID::getotaker().
-
-As a trivial case, a subclass may opt out of supporting virtual fields with
-the following code:
-
-sub virtual_fields { () }
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Create a new record. To add the record to the database, see "insert".
-
-=cut
-
-sub table { 'part_virtual_field'; }
-sub virtual_fields { () }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this record from the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-If there is an error, returns the error, otherwise returns false.
-Called by the insert and replace methods.
-
-=back
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error = $self->ut_text('name') ||
- $self->ut_text('dbtable') ||
- $self->ut_number('length')
- ;
- return $error if $error;
-
- # Make sure it's a real table with a numeric primary key
- my ($table, $pkey);
- if($table = dbdef->table($self->dbtable)) {
- if($pkey = $table->primary_key) {
- if($table->column($pkey)->type =~ /int/i) {
- # this is what it should be
- } else {
- $error = "$table.$pkey is not an integer";
- }
- } else {
- $error = "$table does not have a single-field primary key";
- }
- } else {
- $error = "$table does not exist in the schema";
- }
- return $error if $error;
-
- # Possibly some sanity checks for check_block and list_source?
-
- $self->SUPER::check;
-}
-
-=item list
-
-Evaluates list_source.
-
-=cut
-
-sub list {
- my $self = shift;
- return () unless $self->list_source;
-
- my @opts = eval($self->list_source);
- if($@) {
- warn $@;
- return ();
- } else {
- return @opts;
- }
-}
-
-=item widget UI_TYPE MODE [ VALUE ]
-
-Generates UI code for a widget suitable for editing/viewing the field, based on
-list_source and length.
-
-The only UI_TYPE currently supported is 'HTML', and the only MODE is 'view'.
-Others will be added later.
-
-In HTML, all widgets are assumed to be table rows. View widgets look like
-<TR><TD ALIGN="right">Label</TD><TD BGCOLOR="#ffffff">Value</TD></TR>
-
-(Most of the display style stuff, such as the colors, should probably go into
-a separate module specific to the UI. That can wait, though. The API for
-this function won't change.)
-
-VALUE (optional) is the current value of the field.
-
-=cut
-
-sub widget {
- my $self = shift;
- my ($ui_type, $mode, $value) = @_;
- my $text;
- my $label = $self->label || $self->name;
-
- if ($ui_type eq 'HTML') {
- if ($mode eq 'view') {
- $text = q!<TR><TD ALIGN="right">! . $label .
- q!</TD><TD BGCOLOR="#ffffff">! . $value .
- q!</TD></TR>! . "\n";
- } elsif ($mode eq 'edit') {
- $text = q!<TR><TD ALIGN="right">! . $label .
- q!</TD><TD>!;
- if ($self->list_source) {
- $text .= q!<SELECT NAME="! . $self->name .
- q!" SIZE=1>! . "\n";
- foreach ($self->list) {
- $text .= q!<OPTION VALUE="! . $_ . q!"!;
- $text .= ' SELECTED' if ($_ eq $value);
- $text .= '>' . $_ . '</OPTION>' . "\n";
- }
- } else {
- $text .= q!<INPUT NAME="! . $self->name .
- q!" VALUE="! . escapeHTML($value) . q!"!;
- if ($self->length) {
- $text .= q! SIZE="! . $self->length . q!"!;
- }
- $text .= '>';
- }
- $text .= q!</TD></TR>! . "\n";
- } else {
- return '';
- }
- } else {
- return '';
- }
- return $text;
-}
-
-=head1 NOTES
-
-=head2 Semantics of check_block:
-
-This has been changed from the sb_field implementation to make check_blocks
-simpler and more natural to Perl programmers who work on things other than
-Freeside.
-
-The check_block is eval'd with the (proposed) new value of the field in $_,
-and the object to be updated in $self. Its return value is ignored. The
-check_block may change the value of $_ to override the proposed value, or
-call die() (with an appropriate error message) to reject the update entirely;
-the error string will be returned as the output of the check() method.
-
-This makes check_blocks like
-
-C<s/foo/bar/>
-
-do what you expect.
-
-The check_block is expected NOT to do anything freaky to $self, like modifying
-other fields or calling $self->check(). You have been warned.
-
-(FIXME: Rewrite some of the warnings from part_sb_field and insert here.)
-
-=head1 BUGS
-
-None. It's absolutely falwless.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::virtual_field>
-
-=cut
-
-1;
-
-
diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm
deleted file mode 100644
index 5448b03..0000000
--- a/FS/FS/pay_batch.pm
+++ /dev/null
@@ -1,538 +0,0 @@
-package FS::pay_batch;
-
-use strict;
-use vars qw( @ISA );
-use Time::Local;
-use Text::CSV_XS;
-use FS::Record qw( dbh qsearch qsearchs );
-use FS::cust_pay;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::pay_batch - Object methods for pay_batch records
-
-=head1 SYNOPSIS
-
- use FS::pay_batch;
-
- $record = new FS::pay_batch \%hash;
- $record = new FS::pay_batch { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::pay_batch object represents an payment batch. FS::pay_batch inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item batchnum - primary key
-
-=item payby - CARD or CHEK
-
-=item status - O (Open), I (In-transit), or R (Resolved)
-
-=item download -
-
-=item upload -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new batch. To add the batch to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'pay_batch'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid batch. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('batchnum')
- || $self->ut_enum('payby', [ 'CARD', 'CHEK' ])
- || $self->ut_enum('status', [ 'O', 'I', 'R' ])
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item rebalance
-
-=cut
-
-sub rebalance {
- my $self = shift;
-}
-
-=item set_status
-
-=cut
-
-sub set_status {
- my $self = shift;
- $self->status(shift);
- $self->download(time)
- if $self->status eq 'I' && ! $self->download;
- $self->upload(time)
- if $self->status eq 'R' && ! $self->upload;
- $self->replace();
-}
-
-=item import_results OPTION => VALUE, ...
-
-Import batch results.
-
-Options are:
-
-I<filehandle> - open filehandle of results file.
-
-I<format> - "csv-td_canada_trust-merchant_pc_batch", "csv-chase_canada-E-xactBatch", "ach-spiritone", or "PAP"
-
-=cut
-
-sub import_results {
- my $self = shift;
-
- my $param = ref($_[0]) ? shift : { @_ };
- my $fh = $param->{'filehandle'};
- my $format = $param->{'format'};
-
- my $filetype; # CSV, Fixed80, Fixed264
- my @fields;
- my $formatre; # for Fixed.+
- my @values;
- my $begin_condition;
- my $end_condition;
- my $end_hook;
- my $hook;
- my $approved_condition;
- my $declined_condition;
-
- if ( $format eq 'csv-td_canada_trust-merchant_pc_batch' ) {
-
- $filetype = "CSV";
-
- @fields = (
- 'paybatchnum', # Reference#: Invoice number of the transaction
- 'paid', # Amount: Amount of the transaction. Dollars and cents
- # with no decimal entered.
- '', # Card Type: 0 - MCrd, 1 - Visa, 2 - AMEX, 3 - Discover,
- # 4 - Insignia, 5 - Diners/EnRoute, 6 - JCB
- '_date', # Transaction Date: Date the Transaction was processed
- 'time', # Transaction Time: Time the transaction was processed
- 'payinfo', # Card Number: Card number for the transaction
- '', # Expiry Date: Expiry date of the card
- '', # Auth#: Authorization number entered for force post
- # transaction
- 'type', # Transaction Type: 0 - purchase, 40 - refund,
- # 20 - force post
- 'result', # Processing Result: 3 - Approval,
- # 4 - Declined/Amount over limit,
- # 5 - Invalid/Expired/stolen card,
- # 6 - Comm Error
- '', # Terminal ID: Terminal ID used to process the transaction
- );
-
- $end_condition = sub {
- my $hash = shift;
- $hash->{'type'} eq '0BC';
- };
-
- $end_hook = sub {
- my( $hash, $total) = @_;
- $total = sprintf("%.2f", $total);
- my $batch_total = sprintf("%.2f", $hash->{'paybatchnum'} / 100 );
- return "Our total $total does not match bank total $batch_total!"
- if $total != $batch_total;
- '';
- };
-
- $hook = sub {
- my $hash = shift;
- $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 );
- $hash->{'_date'} = timelocal( substr($hash->{'time'}, 4, 2),
- substr($hash->{'time'}, 2, 2),
- substr($hash->{'time'}, 0, 2),
- substr($hash->{'_date'}, 6, 2),
- substr($hash->{'_date'}, 4, 2)-1,
- substr($hash->{'_date'}, 0, 4)-1900, );
- };
-
- $approved_condition = sub {
- my $hash = shift;
- $hash->{'type'} eq '0' && $hash->{'result'} == 3;
- };
-
- $declined_condition = sub {
- my $hash = shift;
- $hash->{'type'} eq '0' && ( $hash->{'result'} == 4
- || $hash->{'result'} == 5 );
- };
-
-
- }elsif ( $format eq 'csv-chase_canada-E-xactBatch' ) {
-
- $filetype = "CSV";
-
- @fields = (
- '', # Internal(bank) id of the transaction
- '', # Transaction Type: 00 - purchase, 01 - preauth,
- # 02 - completion, 03 - forcepost,
- # 04 - refund, 05 - auth,
- # 06 - purchase corr, 07 - refund corr,
- # 08 - void 09 - void return
- '', # gateway used to process this transaction
- 'paid', # Amount: Amount of the transaction. Dollars and cents
- # with decimal entered.
- 'auth', # Auth#: Authorization number (if approved)
- 'payinfo', # Card Number: Card number for the transaction
- '', # Expiry Date: Expiry date of the card
- '', # Cardholder Name
- 'bankcode', # Bank response code (3 alphanumeric)
- 'bankmess', # Bank response message
- 'etgcode', # ETG response code (2 alphanumeric)
- 'etgmess', # ETG response message
- '', # Returned customer number for the transaction
- 'paybatchnum', # Reference#: paybatch number of the transaction
- '', # Reference#: Invoice number of the transaction
- 'result', # Processing Result: Approved of Declined
- );
-
- $end_condition = sub {
- '';
- };
-
- $hook = sub {
- my $hash = shift;
- my $cpb = shift;
- $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'}); #hmmmm
- $hash->{'_date'} = time; # got a better one?
- $hash->{'payinfo'} = $cpb->{'payinfo'}
- if( substr($hash->{'payinfo'}, -4) eq substr($cpb->{'payinfo'}, -4) );
- };
-
- $approved_condition = sub {
- my $hash = shift;
- $hash->{'etgcode'} eq '00' && $hash->{'result'} eq "Approved";
- };
-
- $declined_condition = sub {
- my $hash = shift;
- $hash->{'etgcode'} ne '00' # internal processing error
- || ( $hash->{'result'} eq "Declined" );
- };
-
-
- }elsif ( $format eq 'PAP' ) {
-
- $filetype = "Fixed264";
-
- @fields = (
- 'recordtype', # We are interested in the 'D' or debit records
- 'batchnum', # Record#: batch number we used when sending the file
- 'datacenter', # Where in the bowels of the bank the data was processed
- 'paid', # Amount: Amount of the transaction. Dollars and cents
- # with no decimal entered.
- '_date', # Transaction Date: Date the Transaction was processed
- 'bank', # Routing information
- 'payinfo', # Account number for the transaction
- 'paybatchnum', # Reference#: Invoice number of the transaction
- );
-
- $formatre = '^(.).{19}(.{4})(.{3})(.{10})(.{6})(.{9})(.{12}).{110}(.{19}).{71}$';
-
- $end_condition = sub {
- my $hash = shift;
- $hash->{'recordtype'} eq 'W';
- };
-
- $end_hook = sub {
- my( $hash, $total) = @_;
- $total = sprintf("%.2f", $total);
- my $batch_total = $hash->{'datacenter'}.$hash->{'paid'}.
- substr($hash->{'_date'},0,1); # YUCK!
- $batch_total = sprintf("%.2f", $batch_total / 100 );
- return "Our total $total does not match bank total $batch_total!"
- if $total != $batch_total;
- '';
- };
-
- $hook = sub {
- my $hash = shift;
- $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 );
- my $tmpdate = timelocal( 0,0,1,1,0,substr($hash->{'_date'}, 0, 3)+2000);
- $tmpdate += 86400*(substr($hash->{'_date'}, 3, 3)-1) ;
- $hash->{'_date'} = $tmpdate;
- $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'bank'};
- };
-
- $approved_condition = sub {
- 1;
- };
-
- $declined_condition = sub {
- 0;
- };
-
- }elsif ( $format eq 'ach-spiritone' ) {
-
- $filetype = "CSV";
-
- @fields = (
- '', # Name
- 'paybatchnum', # ID: Number of the transaction
- 'aba', # ABA Number for the transaction
- 'payinfo', # Bank Account Number for the transaction
- '', # Transaction Type: 27 - debit
- 'paid', # Amount: Amount of the transaction. Dollars and cents
- # with decimal entered.
- '', # Default Transaction Type
- '', # Default Amount: Dollars and cents with decimal entered.
- );
-
- $end_condition = sub {
- '';
- };
-
- $hook = sub {
- my $hash = shift;
- $hash->{'_date'} = time; # got a better one?
- $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'aba'};
- };
-
- $approved_condition = sub {
- 1;
- };
-
- $declined_condition = sub {
- 0;
- };
-
-
- } else {
- return "Unknown format $format";
- }
-
- my $csv = new Text::CSV_XS;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $reself = $self->select_for_update;
-
- unless ( $reself->status eq 'I' ) {
- $dbh->rollback if $oldAutoCommit;
- return "batchnum ". $self->batchnum. "no longer in transit";
- };
-
- my $error = $self->set_status('R');
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error
- }
-
- my $total = 0;
- my $line;
- while ( defined($line=<$fh>) ) {
-
- next if $line =~ /^\s*$/; #skip blank lines
-
- if ($filetype eq "CSV") {
- $csv->parse($line) or do {
- $dbh->rollback if $oldAutoCommit;
- return "can't parse: ". $csv->error_input();
- };
- @values = $csv->fields();
- }elsif ($filetype eq "Fixed80" || $filetype eq "Fixed264"){
- @values = $line =~ /$formatre/;
- unless (@values) {
- $dbh->rollback if $oldAutoCommit;
- return "can't parse: ". $line;
- };
- }else{
- $dbh->rollback if $oldAutoCommit;
- return "Unknown file type $filetype";
- }
-
- my %hash;
- foreach my $field ( @fields ) {
- my $value = shift @values;
- next unless $field;
- $hash{$field} = $value;
- }
-
- if ( &{$end_condition}(\%hash) ) {
- my $error = &{$end_hook}(\%hash, $total);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- last;
- }
-
- my $cust_pay_batch =
- qsearchs('cust_pay_batch', { 'paybatchnum' => $hash{'paybatchnum'}+0 } );
- unless ( $cust_pay_batch ) {
- return "unknown paybatchnum $hash{'paybatchnum'}\n";
- }
- my $custnum = $cust_pay_batch->custnum,
- my $payby = $cust_pay_batch->payby,
-
- my $new_cust_pay_batch = new FS::cust_pay_batch { $cust_pay_batch->hash };
-
- &{$hook}(\%hash, $cust_pay_batch->hashref);
-
- if ( &{$approved_condition}(\%hash) ) {
-
- $new_cust_pay_batch->status('Approved');
-
- } elsif ( &{$declined_condition}(\%hash) ) {
-
- $new_cust_pay_batch->status('Declined');
-
- }
-
- my $error = $new_cust_pay_batch->replace($cust_pay_batch);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error updating status of paybatchnum $hash{'paybatchnum'}: $error\n";
- }
-
- if ( $new_cust_pay_batch->status =~ /Approved/i ) {
-
- my $cust_pay = new FS::cust_pay ( {
- 'custnum' => $custnum,
- 'payby' => $payby,
- 'paybatch' => $self->batchnum,
- map { $_ => $hash{$_} } (qw( paid _date payinfo )),
- } );
- $error = $cust_pay->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error adding payment paybatchnum $hash{'paybatchnum'}: $error\n";
- }
- $total += $hash{'paid'};
-
- $cust_pay->cust_main->apply_payments;
-
- } elsif ( $new_cust_pay_batch->status =~ /Declined/i ) {
-
- #false laziness w/cust_main::collect
-
- my $due_cust_event = $new_cust_pay_batch->cust_main->due_cust_event(
- #'check_freq' => '1d', #?
- 'eventtable' => 'cust_pay_batch',
- 'objects' => [ $new_cust_pay_batch ],
- );
- unless( ref($due_cust_event) ) {
- $dbh->rollback if $oldAutoCommit;
- return $due_cust_event;
- }
-
- foreach my $cust_event ( @$due_cust_event ) {
-
- #XXX lock event
-
- #re-eval event conditions (a previous event could have changed things)
- next unless $cust_event->test_conditions;
-
- if ( my $error = $cust_event->do_event() ) {
- # gah, even with transactions.
- #$dbh->commit if $oldAutoCommit; #well.
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- }
-
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=back
-
-=head1 BUGS
-
-status is somewhat redundant now that download and upload exist
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm
deleted file mode 100644
index 6684c95..0000000
--- a/FS/FS/payby.pm
+++ /dev/null
@@ -1,185 +0,0 @@
-package FS::payby;
-
-use strict;
-use vars qw(%hash %payby2bop);
-use Tie::IxHash;
-use Business::CreditCard;
-
-
-=head1 NAME
-
-FS::payby - Object methods for payment type records
-
-=head1 SYNOPSIS
-
- use FS::payby;
-
- #for now...
-
- my @payby = FS::payby->payby;
-
- my $bool = FS::payby->can_payby('cust_main', 'CARD');
-
- tie my %payby, 'Tie::IxHash', FS::payby->payby2longname
-
- my @cust_payby = FS::payby->cust_payby;
-
- tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname
-
-=head1 DESCRIPTION
-
-Payment types.
-
-=head1 METHODS
-
-=over 4
-
-=item
-
-=cut
-
-# paybys can be any/all of:
-# - a customer payment type (cust_main.payby)
-# - a payment or refund type (cust_pay.payby, cust_pay_batch.payby, cust_refund.payby)
-# - an event type (part_bill_event.payby)
-
-tie %hash, 'Tie::IxHash',
- 'CARD' => {
- tinyname => 'card',
- shortname => 'Credit card',
- longname => 'Credit card (automatic)',
- },
- 'DCRD' => {
- tinyname => 'card',
- shortname => 'Credit card',
- longname => 'Credit card (on-demand)',
- cust_pay => 'CARD', #this is a customer type only, payments are CARD...
- },
- 'CHEK' => {
- tinyname => 'check',
- shortname => 'Electronic check',
- longname => 'Electronic check (automatic)',
- },
- 'DCHK' => {
- tinyname => 'check',
- shortname => 'Electronic check',
- longname => 'Electronic check (on-demand)',
- cust_pay => 'CHEK', #this is a customer type only, payments are CHEK...
- },
- 'LECB' => {
- tinyname => 'phone bill',
- shortname => 'Phone bill billing',
- longname => 'Phone bill billing',
- },
- 'BILL' => {
- tinyname => 'billing',
- shortname => 'Billing',
- longname => 'Billing',
- },
- 'PREP' => {
- tinyname => 'prepaid card',
- shortname => 'Prepaid card',
- longname => 'Prepaid card',
- cust_main => 'BILL', #this is a payment type only, customers go to BILL...
- },
- 'CASH' => {
- tinyname => 'cash',
- shortname => 'Cash', # initial payment, then billing
- longname => 'Cash',
- cust_main => 'BILL', #this is a payment type only, customers go to BILL...
- },
- 'WEST' => {
- tinyname => 'western union',
- shortname => 'Western Union', # initial payment, then billing
- longname => 'Western Union',
- cust_main => 'BILL', #this is a payment type only, customers go to BILL...
- },
- 'MCRD' => { #not the same as DCRD
- tinyname => 'card',
- shortname => 'Manual credit card', # initial payment, then billing
- longname => 'Manual credit card',
- cust_main => 'BILL', #this is a payment type only, customers go to BILL...
- },
- 'COMP' => {
- tinyname => 'comp',
- shortname => 'Complimentary',
- longname => 'Complimentary',
- cust_pay => '', # (free) is depricated as a payment type in cust_pay
- },
- 'CBAK' => {
- tinyname => 'chargeback',
- shortname => 'Chargeback',
- longname => 'Chargeback',
- cust_main => '', # not a customer type
- },
-;
-
-sub payby {
- keys %hash;
-}
-
-sub can_payby {
- my( $self, $table, $payby ) = @_;
-
- #return "Illegal payby" unless $hash{$payby};
- return 0 unless $hash{$payby};
-
- $table = 'cust_pay' if $table eq 'cust_pay_batch' || $table eq 'cust_refund';
- return 0 if exists( $hash{$payby}->{$table} );
-
- return 1;
-}
-
-sub payby2longname {
- my $self = shift;
- map { $_ => $hash{$_}->{longname} } $self->payby;
-}
-
-sub shortname {
- my( $self, $payby ) = @_;
- $hash{$payby}->{shortname};
-}
-
-sub longname {
- my( $self, $payby ) = @_;
- $hash{$payby}->{longname};
-}
-
-%payby2bop = (
- 'CARD' => 'CC',
- 'CHEK' => 'ECHECK',
-);
-
-sub payby2bop {
- my( $self, $payby ) = @_;
- $payby2bop{ $self->payby2payment($payby) };
-}
-
-sub payby2payment {
- my( $self, $payby ) = @_;
- $hash{$payby}{'cust_pay'} || $payby;
-}
-
-sub cust_payby {
- my $self = shift;
- grep { ! exists $hash{$_}->{cust_main} } $self->payby;
-}
-
-sub cust_payby2longname {
- my $self = shift;
- map { $_ => $hash{$_}->{longname} } $self->cust_payby;
-}
-
-=back
-
-=head1 BUGS
-
-This should eventually be an actual database table, and all tables that
-currently have a char payby field should have a foreign key into here instead.
-
-=head1 SEE ALSO
-
-=cut
-
-1;
-
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
deleted file mode 100644
index 15c4e39..0000000
--- a/FS/FS/payinfo_Mixin.pm
+++ /dev/null
@@ -1,249 +0,0 @@
-package FS::payinfo_Mixin;
-
-use strict;
-use Business::CreditCard;
-use FS::payby;
-
-=head1 NAME
-
-FS::payinfo_Mixin - Mixin class for records in tables that contain payinfo.
-
-=head1 SYNOPSIS
-
-package FS::some_table;
-use vars qw(@ISA);
-@ISA = qw( FS::payinfo_Mixin FS::Record );
-
-=head1 DESCRIPTION
-
-This is a mixin class for records that contain payinfo.
-
-This class handles the following functions for payinfo...
-
-Payment Mask (Generation and Storage)
-Data Validation (parent checks need to be sure to call this)
-Encryption - In the Future (Pull from Record.pm)
-Bad Card Stuff - In the Future (Integrate Banned Pay)
-Currency - In the Future
-
-=head1 FIELDS
-
-=over 4
-
-=item payby
-
-The following payment types (payby) are supported:
-
-For Customers (cust_main):
-'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand),
-'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand),
-'LECB' (Phone bill billing), 'BILL' (billing), 'COMP' (free), or
-'PREPAY' (special billing type: applies a credit and sets billing type to I<BILL> - see L<FS::prepay_credit>)
-
-For Refunds (cust_refund):
-'CARD' (credit cards), 'CHEK' (electronic check/ACH),
-'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash),
-'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' Chargeback, or 'COMP' (free)
-
-
-For Payments (cust_pay):
-'CARD' (credit cards), 'CHEK' (electronic check/ACH),
-'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card),
-'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card)
-'COMP' (free) is depricated as a payment type in cust_pay
-
-=cut
-
-# was this supposed to do something?
-
-#sub payby {
-# my($self,$payby) = @_;
-# if ( defined($payby) ) {
-# $self->setfield('payby', $payby);
-# }
-# return $self->getfield('payby')
-#}
-
-=item payinfo
-
-Payment information (payinfo) can be one of the following types:
-
-Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>)
-
-=cut
-
-sub payinfo {
- my($self,$payinfo) = @_;
- if ( defined($payinfo) ) {
- $self->setfield('payinfo', $payinfo); # This is okay since we are the 'setter'
- $self->paymask($self->mask_payinfo());
- } else {
- $payinfo = $self->getfield('payinfo'); # This is okay since we are the 'getter'
- return $payinfo;
- }
-}
-
-=item paycvv
-
-Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card
-
-=cut
-
-sub paycvv {
- my($self,$paycvv) = @_;
- # This is only allowed in cust_main... Even then it really shouldn't be stored...
- if ($self->table eq 'cust_main') {
- if ( defined($paycvv) ) {
- $self->setfield('paycvv', $paycvv); # This is okay since we are the 'setter'
- } else {
- $paycvv = $self->getfield('paycvv'); # This is okay since we are the 'getter'
- return $paycvv;
- }
- } else {
-# warn "This doesn't work for other tables besides cust_main
- '';
- }
-}
-
-=item paymask
-
-=cut
-
-sub paymask {
- my($self, $paymask) = @_;
-
- if ( defined($paymask) && $paymask ne '' ) {
- # I hate this little bit of magic... I don't expect it to cause a problem,
- # but who knows... If the payinfo is passed in masked then ignore it and
- # set it based on the payinfo. The only guy that should call this in this
- # way is... $self->payinfo
- $self->setfield('paymask', $self->mask_payinfo());
-
- } else {
-
- $paymask=$self->getfield('paymask');
- if (!defined($paymask) || $paymask eq '') {
- # Generate it if it's blank - Note that we're not going to set it - just
- # generate
- $paymask = $self->mask_payinfo();
- }
-
- }
-
- return $paymask;
-}
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item mask_payinfo [ PAYBY, PAYINFO ]
-
-This method converts the payment info (credit card, bank account, etc.) into a
-masked string.
-
-Optionally, an arbitrary payby and payinfo can be passed.
-
-=cut
-
-sub mask_payinfo {
- my $self = shift;
- my $payby = scalar(@_) ? shift : $self->payby;
- my $payinfo = scalar(@_) ? shift : $self->payinfo;
-
- # Check to see if it's encrypted...
- my $paymask;
- if ( $self->is_encrypted($payinfo) ) {
- $paymask = 'N/A';
- } else {
- # if not, mask it...
- if ($payby eq 'CARD' || $payby eq 'DCRD' || $payby eq 'MCRD') {
- # Credit Cards
- my $conf = new FS::Conf;
- my $mask_method = $conf->config('card_masking_method') || 'first6last4';
- $mask_method =~ /^first(\d+)last(\d+)$/
- or die "can't parse card_masking_method $mask_method";
- my($first, $last) = ($1, $2);
-
- $paymask = substr($payinfo,0,$first).
- 'x'x(length($payinfo)-$first-$last).
- substr($payinfo,(length($payinfo)-$last));
- } elsif ($payby eq 'CHEK' || $payby eq 'DCHK' ) {
- # Checks (Show last 2 @ bank)
- my( $account, $aba ) = split('@', $payinfo );
- $paymask = 'x'x(length($account)-2).
- substr($account,(length($account)-2))."@".$aba;
- } else { # Tie up loose ends
- $paymask = $payinfo;
- }
- }
- return $paymask;
-}
-
-=cut
-
-sub _mask_payinfo {
- my $self = shift;
-
-=item payinfo_check
-
-Checks payby and payinfo.
-
-For Customers (cust_main):
-'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand),
-'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand),
-'LECB' (Phone bill billing), 'BILL' (billing), 'COMP' (free), or
-'PREPAY' (special billing type: applies a credit - see L<FS::prepay_credit> and sets billing type to I<BILL>)
-
-For Refunds (cust_refund):
-'CARD' (credit cards), 'CHEK' (electronic check/ACH),
-'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash),
-'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' (Chargeback), or 'COMP' (free)
-
-For Payments (cust_pay):
-'CARD' (credit cards), 'CHEK' (electronic check/ACH),
-'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card),
-'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card)
-'COMP' (free) is depricated as a payment type in cust_pay
-
-=cut
-
-sub payinfo_check {
- my $self = shift;
-
- FS::payby->can_payby($self->table, $self->payby)
- or return "Illegal payby: ". $self->payby;
-
- if ( $self->payby eq 'CARD' ) {
- my $payinfo = $self->payinfo;
- $payinfo =~ s/\D//g;
- $self->payinfo($payinfo);
- if ( $self->payinfo ) {
- $self->payinfo =~ /^(\d{13,16})$/
- or return "Illegal (mistyped?) credit card number (payinfo)";
- $self->payinfo($1);
- validate($self->payinfo) or return "Illegal credit card number";
- return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
- } else {
- $self->payinfo('N/A');
- }
- } else {
- my $error = $self->ut_textn('payinfo');
- return $error if $error;
- }
-}
-
-=head1 BUGS
-
-Have to add the future items...
-
-=head1 SEE ALSO
-
-L<FS::payby>, L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/FS/payment_gateway.pm b/FS/FS/payment_gateway.pm
deleted file mode 100644
index 35b4f08..0000000
--- a/FS/FS/payment_gateway.pm
+++ /dev/null
@@ -1,200 +0,0 @@
-package FS::payment_gateway;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs dbh );
-use FS::option_Common;
-use FS::agent_payment_gateway;
-
-@ISA = qw( FS::option_Common );
-
-=head1 NAME
-
-FS::payment_gateway - Object methods for payment_gateway records
-
-=head1 SYNOPSIS
-
- use FS::payment_gateway;
-
- $record = new FS::payment_gateway \%hash;
- $record = new FS::payment_gateway { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::payment_gateway object represents an payment gateway.
-FS::payment_gateway inherits from FS::Record. The following fields are
-currently supported:
-
-=over 4
-
-=item gatewaynum - primary key
-
-=item gateway_module - Business::OnlinePayment:: module name
-
-=item gateway_username - payment gateway username
-
-=item gateway_password - payment gateway password
-
-=item gateway_action - optional action or actions (multiple actions are separated with `,': for example: `Authorization Only, Post Authorization'). Defaults to `Normal Authorization'.
-
-=item disabled - Disabled flag, empty or 'Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new payment gateway. To add the payment gateway to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'payment_gateway'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid payment gateway. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('gatewaynum')
- || $self->ut_alpha('gateway_module')
- || $self->ut_textn('gateway_username')
- || $self->ut_anything('gateway_password')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- #|| $self->ut_textn('gateway_action')
- ;
- return $error if $error;
-
- if ( $self->gateway_action ) {
- my @actions = split(/,\s*/, $self->gateway_action);
- $self->gateway_action(
- join( ',', map { /^(Normal Authorization|Authorization Only|Credit|Post Authorization)$/
- or return "Unknown action $_";
- $1
- }
- @actions
- )
- );
- } else {
- $self->gateway_action('Normal Authorization');
- }
-
- $self->SUPER::check;
-}
-
-=item agent_payment_gateway
-
-Returns any agent overrides for this payment gateway.
-
-=cut
-
-sub agent_payment_gateway {
- my $self = shift;
- qsearch('agent_payment_gateway', { 'gatewaynum' => $self->gatewaynum } );
-}
-
-=item disable
-
-Disables this payment gateway: deletes all associated agent_payment_gateway
-overrides and sets the I<disabled> field to "B<Y>".
-
-=cut
-
-sub disable {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $agent_payment_gateway ( $self->agent_payment_gateway ) {
- my $error = $agent_payment_gateway->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error deleting agent_payment_gateway override: $error";
- }
- }
-
- $self->disabled('Y');
- my $error = $self->replace();
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error disabling payment_gateway: $error";
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/payment_gateway_option.pm b/FS/FS/payment_gateway_option.pm
deleted file mode 100644
index 0576022..0000000
--- a/FS/FS/payment_gateway_option.pm
+++ /dev/null
@@ -1,126 +0,0 @@
-package FS::payment_gateway_option;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::payment_gateway_option - Object methods for payment_gateway_option records
-
-=head1 SYNOPSIS
-
- use FS::payment_gateway_option;
-
- $record = new FS::payment_gateway_option \%hash;
- $record = new FS::payment_gateway_option { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::payment_gateway_option object represents an option key and value for
-a payment gateway. FS::payment_gateway_option inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item optionnum - primary key
-
-=item gatewaynum -
-
-=item optionname -
-
-=item optionvalue -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new option. To add the option to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'payment_gateway_option'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid option. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('optionnum')
- || $self->ut_foreign_key('gatewaynum', 'payment_gateway', 'gatewaynum')
- || $self->ut_text('optionname')
- || $self->ut_textn('optionvalue')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/pkg_class.pm b/FS/FS/pkg_class.pm
deleted file mode 100644
index bab6e5e..0000000
--- a/FS/FS/pkg_class.pm
+++ /dev/null
@@ -1,113 +0,0 @@
-package FS::pkg_class;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch );
-use FS::part_pkg;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::pkg_class - Object methods for pkg_class records
-
-=head1 SYNOPSIS
-
- use FS::pkg_class;
-
- $record = new FS::pkg_class \%hash;
- $record = new FS::pkg_class { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::pkg_class object represents an package class. Every package definition
-(see L<FS::part_pkg>) has, optionally, a package class. FS::pkg_class inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item classnum - primary key (assigned automatically for new package classes)
-
-=item classname - Text name of this package class
-
-=item disabled - Disabled flag, empty or 'Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new package class. To add the package class to the database, see
-L<"insert">.
-
-=cut
-
-sub table { 'pkg_class'; }
-
-=item insert
-
-Adds this package class to the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item delete
-
-Deletes this package class from the database. Only package classes with no
-associated package definitions can be deleted. If there is an error, returns
-the error, otherwise returns false.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- return "Can't delete an pkg_class with part_pkg records!"
- if qsearch( 'part_pkg', { 'classnum' => $self->classnum } );
-
- $self->SUPER::delete;
-}
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid package class. If there is an
-error, returns the error, otherwise returns false. Called by the insert and
-replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('classnum')
- or $self->ut_text('classname')
- or $self->SUPER::check;
-
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::part_pkg>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/pkg_referral.pm b/FS/FS/pkg_referral.pm
deleted file mode 100644
index 333c2bf..0000000
--- a/FS/FS/pkg_referral.pm
+++ /dev/null
@@ -1,126 +0,0 @@
-package FS::pkg_referral;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::pkg_referral - Object methods for pkg_referral records
-
-=head1 SYNOPSIS
-
- use FS::pkg_referral;
-
- $record = new FS::pkg_referral \%hash;
- $record = new FS::pkg_referral { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::pkg_referral object represents the association of an advertising source
-with a specific customer package (purchase). FS::pkg_referral inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item pkgrefnum - primary key
-
-=item pkgnum - Customer package. See L<FS::cust_pkg>
-
-=item refnum - Advertising source. See L<FS::part_referral>
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'pkg_referral'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('pkgrefnum')
- || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum' )
- || $self->ut_foreign_key('refnum', 'part_referral', 'refnum' )
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-Multiple pkg_referral records for a single package (configured off by default)
-still seems weird.
-
-=head1 SEE ALSO
-
-L<FS::part_referral>, L<FS::cust_pkg>, L<FS::Record>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/pkg_svc.pm b/FS/FS/pkg_svc.pm
deleted file mode 100644
index 9f3a4a1..0000000
--- a/FS/FS/pkg_svc.pm
+++ /dev/null
@@ -1,160 +0,0 @@
-package FS::pkg_svc;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearchs );
-use FS::part_pkg;
-use FS::part_svc;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::pkg_svc - Object methods for pkg_svc records
-
-=head1 SYNOPSIS
-
- use FS::pkg_svc;
-
- $record = new FS::pkg_svc \%hash;
- $record = new FS::pkg_svc { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $part_pkg = $record->part_pkg;
-
- $part_svc = $record->part_svc;
-
-=head1 DESCRIPTION
-
-An FS::pkg_svc record links a billing item definition (see L<FS::part_pkg>) to
-a service definition (see L<FS::part_svc>). FS::pkg_svc inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item pkgsvcnum - primary key
-
-=item pkgpart - Billing item definition (see L<FS::part_pkg>)
-
-=item svcpart - Service definition (see L<FS::part_svc>)
-
-=item quantity - Quantity of this service definition that this billing item
-definition includes
-
-=item primary_svc - primary flag, empty or 'Y'
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Create a new record. To add the record to the database, see L<"insert">.
-
-=cut
-
-sub table { 'pkg_svc'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this record from the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my( $new, $old ) = ( shift, shift );
-
- $old = $new->replace_old unless defined($old);
-
- return "Can't change pkgpart!" if $old->pkgpart != $new->pkgpart;
- return "Can't change svcpart!" if $old->svcpart != $new->svcpart;
-
- $new->SUPER::replace($old);
-}
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error;
- $error =
- $self->ut_numbern('pkgsvcnum')
- || $self->ut_number('pkgpart')
- || $self->ut_number('svcpart')
- || $self->ut_number('quantity')
- ;
- return $error if $error;
-
- return "Unknown pkgpart!" unless $self->part_pkg;
- return "Unknown svcpart!" unless $self->part_svc;
-
- if ( $self->dbdef_table->column('primary_svc') ) {
- $error = $self->ut_enum('primary_svc', [ '', 'Y' ] );
- return $error if $error;
- }
-
- $self->SUPER::check;
-}
-
-=item part_pkg
-
-Returns the FS::part_pkg object (see L<FS::part_pkg>).
-
-=cut
-
-sub part_pkg {
- my $self = shift;
- qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
-}
-
-=item part_svc
-
-Returns the FS::part_svc object (see L<FS::part_svc>).
-
-=cut
-
-sub part_svc {
- my $self = shift;
- qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::part_pkg>, L<FS::part_svc>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/port.pm b/FS/FS/port.pm
deleted file mode 100644
index c26ca85..0000000
--- a/FS/FS/port.pm
+++ /dev/null
@@ -1,154 +0,0 @@
-package FS::port;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearchs );
-use FS::nas;
-use FS::session;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::port - Object methods for port records
-
-=head1 SYNOPSIS
-
- use FS::port;
-
- $record = new FS::port \%hash;
- $record = new FS::port { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $session = $port->session;
-
-=head1 DESCRIPTION
-
-An FS::port object represents an individual port on a NAS. FS::port inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item portnum - primary key
-
-=item ip - IP address of this port
-
-=item nasport - port number on the NAS
-
-=item nasnum - NAS this port is on - see L<FS::nas>
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new port. To add the port to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'port'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid port. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
- my $error =
- $self->ut_numbern('portnum')
- || $self->ut_ipn('ip')
- || $self->ut_numbern('nasport')
- || $self->ut_number('nasnum');
- ;
- return $error if $error;
- return "Either ip or nasport must be specified"
- unless $self->ip || $self->nasport;
- return "Unknown nasnum"
- unless qsearchs('nas', { 'nasnum' => $self->nasnum } );
- $self->SUPER::check;
-}
-
-=item session
-
-Returns the currently open session on this port, or if no session is currently
-open, the most recent session. See L<FS::session>.
-
-=cut
-
-sub session {
- my $self = shift;
- qsearchs('session', { 'portnum' => $self->portnum }, '*',
- 'ORDER BY login DESC LIMIT 1' );
-}
-
-=back
-
-=head1 BUGS
-
-The session method won't deal well if you have multiple open sessions on a
-port, for example if your RADIUS server drops B<stop> records. Suggestions for
-how to deal with this sort of lossage welcome; should we close the session
-when we get a new session on that port? Tag it as invalid somehow? Close it
-one second after it was opened? *sigh* Maybe FS::session shouldn't let you
-create overlapping sessions, at least folks will find out their logging is
-dropping records.
-
-If you think the above refers multiple user logins you need to read the
-manpages again.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/prepay_credit.pm b/FS/FS/prepay_credit.pm
deleted file mode 100644
index 302ba37..0000000
--- a/FS/FS/prepay_credit.pm
+++ /dev/null
@@ -1,202 +0,0 @@
-package FS::prepay_credit;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw(qsearchs dbh);
-use FS::agent;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::prepay_credit - Object methods for prepay_credit records
-
-=head1 SYNOPSIS
-
- use FS::prepay_credit;
-
- $record = new FS::prepay_credit \%hash;
- $record = new FS::prepay_credit {
- 'identifier' => '4198123455512121'
- 'amount' => '19.95',
- };
-
- $record = new FS::prepay_credit {
- 'identifier' => '4198123455512121'
- 'seconds' => '7200',
- };
-
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::prepay_credit object represents a pre-paid card. FS::prepay_credit
-inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item field - description
-
-=item identifier - identifier entered by the user to receive the credit
-
-=item amount - amount of the credit
-
-=item seconds - time amount of credit (see L<FS::svc_acct/seconds>)
-
-=item agentnum - optional agent (see L<FS::agent>) for this prepaid card
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new pre-paid credit. To add the pre-paid credit to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'prepay_credit'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-=item check
-
-Checks all fields to make sure this is a valid pre-paid credit. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $identifier = $self->identifier;
- $identifier =~ s/\W//g; #anything else would just confuse things
- $self->identifier($identifier);
-
- $self->ut_numbern('prepaynum')
- || $self->ut_alpha('identifier')
- || $self->ut_money('amount')
- || $self->ut_numbern('seconds')
- || $self->ut_numbern('upbytes')
- || $self->ut_numbern('downbytes')
- || $self->ut_numbern('totalbytes')
- || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
- || $self->SUPER::check
- ;
-
-}
-
-=item agent
-
-Returns the agent (see L<FS::agent>) for this prepaid card, if any.
-
-=cut
-
-sub agent {
- my $self = shift;
- qsearchs('agent', { 'agentnum' => $self->agentnum } );
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item generate NUM TYPE HASHREF
-
-Generates the specified number of prepaid cards. Returns an array reference of
-the newly generated card identifiers, or a scalar error message.
-
-=cut
-
-#false laziness w/agent::generate_reg_codes
-sub generate {
- my( $num, $type, $hashref ) = @_;
-
- my @codeset = ();
- push @codeset, ( 'A'..'Z' ) if $type =~ /alpha/;
- push @codeset, ( '1'..'9' ) if $type =~ /numeric/;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $condup = 0; #don't retry forever
-
- my @cards = ();
- for ( 1 ... $num ) {
-
- my $identifier = join('', map($codeset[int(rand $#codeset)], (0..7) ) );
-
- redo if qsearchs('prepay_credit',{identifier=>$identifier}) && $condup++<23;
- $condup = 0;
-
- my $prepay_credit = new FS::prepay_credit {
- 'identifier' => $identifier,
- %$hashref,
- };
- my $error = $prepay_credit->check || $prepay_credit->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "(inserting prepay_credit) $error";
- }
- push @cards, $prepay_credit->identifier;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- \@cards;
-
-}
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::svc_acct>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/queue.pm b/FS/FS/queue.pm
deleted file mode 100644
index 5f8bf11..0000000
--- a/FS/FS/queue.pm
+++ /dev/null
@@ -1,465 +0,0 @@
-package FS::queue;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK $DEBUG $conf $jobnums);
-use Exporter;
-use FS::UID qw(myconnect);
-use FS::Conf;
-use FS::Record qw( qsearch qsearchs dbh );
-#use FS::queue;
-use FS::queue_arg;
-use FS::queue_depend;
-use FS::cust_svc;
-
-@ISA = qw(FS::Record);
-@EXPORT_OK = qw( joblisting );
-
-$DEBUG = 0;
-
-$FS::UID::callback{'FS::queue'} = sub {
- $conf = new FS::Conf;
-};
-
-$jobnums = '';
-
-=head1 NAME
-
-FS::queue - Object methods for queue records
-
-=head1 SYNOPSIS
-
- use FS::queue;
-
- $record = new FS::queue \%hash;
- $record = new FS::queue { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::queue object represents an queued job. FS::queue inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item jobnum - primary key
-
-=item job - fully-qualified subroutine name
-
-=item status - job status
-
-=item statustext - freeform text status message
-
-=item _date - UNIX timestamp
-
-=item svcnum - optional link to service (see L<FS::cust_svc>)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new job. To add the job to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'queue'; }
-
-=item insert [ ARGUMENT, ARGUMENT... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-If any arguments are supplied, a queue_arg record for each argument is also
-created (see L<FS::queue_arg>).
-
-=cut
-
-#false laziness w/part_export.pm
-sub insert {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- foreach my $arg ( @_ ) {
- my $queue_arg = new FS::queue_arg ( {
- 'jobnum' => $self->jobnum,
- 'arg' => $arg,
- } );
- $error = $queue_arg->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- if ( $jobnums ) {
- warn "jobnums global is active: $jobnums\n" if $DEBUG;
- push @$jobnums, $self->jobnum;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item delete
-
-Delete this record from the database. Any corresponding queue_arg records are
-deleted as well
-
-=cut
-
-sub delete {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
- push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } );
-
- my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- foreach my $del ( @del ) {
- $error = $del->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid job. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
- my $error =
- $self->ut_numbern('jobnum')
- || $self->ut_anything('job')
- || $self->ut_numbern('_date')
- || $self->ut_enum('status',['', qw( new locked failed )])
- || $self->ut_anything('statustext')
- || $self->ut_numbern('svcnum')
- ;
- return $error if $error;
-
- $error = $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum');
- $self->svcnum('') if $error;
-
- $self->status('new') unless $self->status;
- $self->_date(time) unless $self->_date;
-
- $self->SUPER::check;
-}
-
-=item args
-
-Returns a list of the arguments associated with this job.
-
-=cut
-
-sub args {
- my $self = shift;
- map $_->arg, qsearch( 'queue_arg',
- { 'jobnum' => $self->jobnum },
- '',
- 'ORDER BY argnum'
- );
-}
-
-=item cust_svc
-
-Returns the FS::cust_svc object associated with this job, if any.
-
-=cut
-
-sub cust_svc {
- my $self = shift;
- qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
-}
-
-=item queue_depend
-
-Returns the FS::queue_depend objects associated with this job, if any.
-(Dependancies that must complete before this job can be run).
-
-=cut
-
-sub queue_depend {
- my $self = shift;
- qsearch('queue_depend', { 'jobnum' => $self->jobnum } );
-}
-
-=item depend_insert OTHER_JOBNUM
-
-Inserts a dependancy for this job - it will not be run until the other job
-specified completes. If there is an error, returns the error, otherwise
-returns false.
-
-When using job dependancies, you should wrap the insertion of all relevant jobs
-in a database transaction.
-
-=cut
-
-sub depend_insert {
- my($self, $other_jobnum) = @_;
- my $queue_depend = new FS::queue_depend ( {
- 'jobnum' => $self->jobnum,
- 'depend_jobnum' => $other_jobnum,
- } );
- $queue_depend->insert;
-}
-
-=item queue_depended
-
-Returns the FS::queue_depend objects that associate other jobs with this job,
-if any. (The jobs that are waiting for this job to complete before they can
-run).
-
-=cut
-
-sub queue_depended {
- my $self = shift;
- qsearch('queue_depend', { 'depend_jobnum' => $self->jobnum } );
-}
-
-=item depended_delete
-
-Deletes the other queued jobs (FS::queue objects) that are waiting for this
-job, if any. If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub depended_delete {
- my $self = shift;
- my $error;
- foreach my $job (
- map { qsearchs('queue', { 'jobnum' => $_->jobnum } ) } $self->queue_depended
- ) {
- $error = $job->depended_delete;
- return $error if $error;
- $error = $job->delete;
- return $error if $error
- }
-}
-
-=item update_statustext VALUE
-
-Updates the statustext value of this job to supplied value, in the database.
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-use vars qw($_update_statustext_dbh);
-sub update_statustext {
- my( $self, $statustext ) = @_;
- return '' if $statustext eq $self->statustext;
- warn "updating statustext for $self to $statustext" if $DEBUG;
-
- $_update_statustext_dbh ||= myconnect;
-
- my $sth = $_update_statustext_dbh->prepare(
- 'UPDATE queue set statustext = ? WHERE jobnum = ?'
- ) or return $_update_statustext_dbh->errstr;
-
- $sth->execute($statustext, $self->jobnum) or return $sth->errstr;
- $_update_statustext_dbh->commit or die $_update_statustext_dbh->errstr;
- $self->statustext($statustext);
- '';
-
- #my $new = new FS::queue { $self->hash };
- #$new->statustext($statustext);
- #my $error = $new->replace($self);
- #return $error if $error;
- #$self->statustext($statustext);
- #'';
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item joblisting HASHREF NOACTIONS
-
-=cut
-
-sub joblisting {
- my($hashref, $noactions) = @_;
-
- use Date::Format;
- use HTML::Entities;
- use FS::CGI;
-
- my @queue = qsearch( 'queue', $hashref );
- return '' unless scalar(@queue);
-
- my $p = FS::CGI::popurl(2);
-
- my $html = qq!<FORM ACTION="$p/misc/queue.cgi" METHOD="POST">!.
- FS::CGI::table(). <<END;
- <TR>
- <TH COLSPAN=2>Job</TH>
- <TH>Args</TH>
- <TH>Date</TH>
- <TH>Status</TH>
-END
- $html .= '<TH>Account</TH>' unless $hashref->{svcnum};
- $html .= '</TR>';
-
- my $dangerous = $conf->exists('queue_dangerous_controls');
-
- my $areboxes = 0;
-
- foreach my $queue ( sort {
- $a->getfield('jobnum') <=> $b->getfield('jobnum')
- } @queue ) {
- my $queue_hashref = $queue->hashref;
- my $jobnum = $queue->jobnum;
-
- my $args;
- if ( $dangerous || $queue->job !~ /^FS::part_export::/ || !$noactions ) {
- $args = encode_entities( join(' ', $queue->args) );
- } else {
- $args = '';
- }
-
- my $date = time2str( "%a %b %e %T %Y", $queue->_date );
- my $status = $queue->status;
- $status .= ': '. $queue->statustext if $queue->statustext;
- my @queue_depend = $queue->queue_depend;
- $status .= ' (waiting for '.
- join(', ', map { $_->depend_jobnum } @queue_depend ).
- ')'
- if @queue_depend;
- my $changable = $dangerous
- || ( ! $noactions && $status =~ /^failed/ || $status =~ /^locked/ );
- if ( $changable ) {
- $status .=
- qq! (&nbsp;<A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A>&nbsp;|!.
- qq!&nbsp;<A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A>&nbsp;)!;
- }
- my $cust_svc = $queue->cust_svc;
-
- $html .= <<END;
- <TR>
- <TD>$jobnum</TD>
- <TD>$queue_hashref->{job}</TD>
- <TD>$args</TD>
- <TD>$date</TD>
- <TD>$status</TD>
-END
-
- unless ( $hashref->{svcnum} ) {
- my $account;
- if ( $cust_svc ) {
- my $table = $cust_svc->part_svc->svcdb;
- my $label = ( $cust_svc->label )[1];
- $account = qq!<A HREF="../view/$table.cgi?!. $queue->svcnum.
- qq!">$label</A>!;
- } else {
- $account = '';
- }
- $html .= "<TD>$account</TD>";
- }
-
- if ( $changable ) {
- $areboxes=1;
- $html .=
- qq!<TD><INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1"></TD>!;
-
- }
-
- $html .= '</TR>';
-
-}
-
- $html .= '</TABLE>';
-
- if ( $areboxes ) {
- $html .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'.
- '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>';
- }
-
- $html;
-
-}
-
-=back
-
-=head1 BUGS
-
-$jobnums global
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/queue_arg.pm b/FS/FS/queue_arg.pm
deleted file mode 100644
index c96ff12..0000000
--- a/FS/FS/queue_arg.pm
+++ /dev/null
@@ -1,117 +0,0 @@
-package FS::queue_arg;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::queue_arg - Object methods for queue_arg records
-
-=head1 SYNOPSIS
-
- use FS::queue_arg;
-
- $record = new FS::queue_arg \%hash;
- $record = new FS::queue_arg { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::queue_arg object represents job argument. FS::queue_arg inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item argnum - primary key
-
-=item jobnum - see L<FS::queue>
-
-=item arg - argument
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new argument. To add the argument to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'queue_arg'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid argument. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
- my $error =
- $self->ut_numbern('argnum')
- || $self->ut_numbern('jobnum')
- || $self->ut_anything('arg')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::queue>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/queue_depend.pm b/FS/FS/queue_depend.pm
deleted file mode 100644
index 99a22c5..0000000
--- a/FS/FS/queue_depend.pm
+++ /dev/null
@@ -1,121 +0,0 @@
-package FS::queue_depend;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::queue;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::queue_depend - Object methods for queue_depend records
-
-=head1 SYNOPSIS
-
- use FS::queue_depend;
-
- $record = new FS::queue_depend \%hash;
- $record = new FS::queue_depend { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::queue_depend object represents an job dependancy. FS::queue_depend
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item dependnum - primary key
-
-=item jobnum - source jobnum (see L<FS::queue>).
-
-=item depend_jobnum - dependancy jobnum (see L<FS::queue>)
-
-=back
-
-The job specified by B<jobnum> depends on the job specified B<depend_jobnum> -
-the B<jobnum> job will not be run until the B<depend_jobnum> job has completed
-successfully (or manually removed).
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new dependancy. To add the dependancy to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'queue_depend'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid dependancy. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('dependnum')
- || $self->ut_foreign_key('jobnum', 'queue', 'jobnum')
- || $self->ut_foreign_key('depend_jobnum', 'queue', 'jobnum')
- || $self->SUPER::check
- ;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::queue>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/raddb.pm b/FS/FS/raddb.pm
deleted file mode 100644
index 506b325..0000000
--- a/FS/FS/raddb.pm
+++ /dev/null
@@ -1,1912 +0,0 @@
-package FS::raddb;
-use vars qw(%attrib);
-
-%attrib = (
- '3com_user_access_level' => '3Com-User-Access-Level',
- '3gpp2_accounting_contain' => '3GPP2-Accounting-Container',
- '3gpp2_acct_stop_trigger' => '3GPP2-Acct-Stop-Trigger',
- '3gpp2_active_time' => '3GPP2-Active-Time',
- '3gpp2_airlink_priority' => '3GPP2-Airlink-Priority',
- '3gpp2_airlink_record_typ' => '3GPP2-Airlink-Record-Type',
- '3gpp2_airlink_sequence_n' => '3GPP2-Airlink-Sequence-Number',
- '3gpp2_allowed_diffserv_m' => '3GPP2-Allowed-Diffserv-Marking',
- '3gpp2_allowed_persistent' => '3GPP2-Allowed-Persistent-TFTs',
- '3gpp2_bad_ppp_frame_coun' => '3GPP2-Bad-PPP-Frame-Count',
- '3gpp2_begin_session' => '3GPP2-Begin-Session',
- '3gpp2_bsid' => '3GPP2-BSID',
- '3gpp2_compulsory_tunnel_' => '3GPP2-Compulsory-Tunnel-Indicator',
- '3gpp2_correlation_id' => '3GPP2-Correlation-Id',
- '3gpp2_dcch_frame_size' => '3GPP2-DCCH-Frame-Size',
- '3gpp2_diffserv_class_opt' => '3GPP2-Diffserv-Class-Option',
- '3gpp2_disconnect_reason' => '3GPP2-Disconnect-Reason',
- '3gpp2_dns_update_capabil' => '3GPP2-DNS-Update-Capability',
- '3gpp2_dns_update_require' => '3GPP2-DNS-Update-Required',
- '3gpp2_esn' => '3GPP2-ESN',
- '3gpp2_fch_frame_size' => '3GPP2-FCH-Frame-Size',
- '3gpp2_foreign_agent_addr' => '3GPP2-Foreign-Agent-Address',
- '3gpp2_forward_dcch_mux_o' => '3GPP2-Forward-DCCH-Mux-Option',
- '3gpp2_forward_dcch_rc' => '3GPP2-Forward-DCCH-RC',
- '3gpp2_forward_fch_mux_op' => '3GPP2-Forward-FCH-Mux-Option',
- '3gpp2_forward_fch_rc' => '3GPP2-Forward-FCH-RC',
- '3gpp2_forward_pdch_rc' => '3GPP2-Forward-PDCH-RC',
- '3gpp2_forward_traffic_ty' => '3GPP2-Forward-Traffic-Type',
- '3gpp2_home_agent_ip_addr' => '3GPP2-Home-Agent-IP-Address',
- '3gpp2_ike_preshared_secr' => '3GPP2-Ike-Preshared-Secret-Request',
- '3gpp2_inbound_mobile_ip_' => '3GPP2-Inbound-Mobile-IP-Sig-Octets',
- '3gpp2_ip_qos' => '3GPP2-IP-QoS',
- '3gpp2_ip_technology' => '3GPP2-IP-Technology',
- '3gpp2_keyid' => '3GPP2-KeyID',
- '3gpp2_last_user_activity' => '3GPP2-Last-User-Activity-Time',
- '3gpp2_mip_lifetime' => '3GPP2-MIP-Lifetime',
- '3gpp2_mn_aaa_removal_ind' => '3GPP2-MN-AAA-Removal-Indication',
- '3gpp2_mn_ha_shared_key' => '3GPP2-MN-HA-Shared-Key',
- '3gpp2_mn_ha_spi' => '3GPP2-MN-HA-SPI',
- '3gpp2_module_orig_term_i' => '3GPP2-Module-Orig-Term-Indicator',
- '3gpp2_number_active_tran' => '3GPP2-Number-Active-Transitions',
- '3gpp2_originating_number' => '3GPP2-Originating-Number-SDBs',
- '3gpp2_originating_sdb_oc' => '3GPP2-Originating-SDB-OCtet-Count',
- '3gpp2_outbound_mobile_ip' => '3GPP2-Outbound-Mobile-IP-Sig-Octets',
- '3gpp2_pcf_ip_address' => '3GPP2-PCF-IP-Address',
- '3gpp2_pre_shared_secret' => '3GPP2-Pre-Shared-Secret',
- '3gpp2_prepaid_acct_capab' => '3GPP2-Prepaid-acct-Capability',
- '3gpp2_prepaid_acct_quota' => '3GPP2-Prepaid-Acct-Quota',
- '3gpp2_prepaid_tariff_swi' => '3GPP2-PrePaid-Tariff-Switching',
- '3gpp2_received_hdlc_octe' => '3GPP2-Received-HDLC-Octets',
- '3gpp2_release_indicator' => '3GPP2-Release-Indicator',
- '3gpp2_remote_address_tab' => '3GPP2-Remote-Address-Table-Index',
- '3gpp2_remote_ip_address' => '3GPP2-Remote-IP-Address',
- '3gpp2_remote_ipv4_addr_o' => '3GPP2-Remote-IPv4-Addr-Octet-Count',
- '3gpp2_remote_ipv6_addres' => '3GPP2-Remote-IPv6-Address',
- '3gpp2_remote_ipv6_octet_' => '3GPP2-Remote-IPv6-Octet-Count',
- '3gpp2_reverse_dcch_mux_o' => '3GPP2-Reverse-DCCH-Mux-Option',
- '3gpp2_reverse_dhhc_rc' => '3GPP2-Reverse-DHHC-RC',
- '3gpp2_reverse_fch_mux_op' => '3GPP2-Reverse-FCH-Mux-Option',
- '3gpp2_reverse_fch_rc' => '3GPP2-Reverse-FCH-RC',
- '3gpp2_reverse_traffic_ty' => '3GPP2-Reverse-Traffic-Type',
- '3gpp2_reverse_tunnel_spe' => '3GPP2-Reverse-Tunnel-Spec',
- '3gpp2_rn_packet_data_ina' => '3GPP2-RN-Packet-Data-Inactivity-Timer',
- '3gpp2_s_key' => '3GPP2-S-Key',
- '3gpp2_s_lifetime' => '3GPP2-S-Lifetime',
- '3gpp2_s_request' => '3GPP2-S-Request',
- '3gpp2_security_level' => '3GPP2-Security-Level',
- '3gpp2_service_option' => '3GPP2-Service-Option',
- '3gpp2_service_option_pro' => '3GPP2-Service-Option-Profile',
- '3gpp2_service_reference_' => '3GPP2-Service-Reference-Id',
- '3gpp2_session_continue' => '3GPP2-Session-Continue',
- '3gpp2_session_terminatio' => '3GPP2-Session-Termination-Capability',
- '3gpp2_terminating_number' => '3GPP2-Terminating-Number-SDBs',
- '3gpp2_terminating_sdb_oc' => '3GPP2-Terminating-SDB-Octet-Count',
- '3gpp2_user_id' => '3GPP2-User-Id',
- '3gpp_charging_characteri' => '3GPP-Charging-Characteristics',
- '3gpp_charging_gateway_ad' => '3GPP-Charging-Gateway-Address',
- '3gpp_charging_gateway_ip' => '3GPP-Charging-Gateway-IPv6-Address',
- '3gpp_charging_id' => '3GPP-Charging-ID',
- '3gpp_ggsn_address' => '3GPP-GGSN-Address',
- '3gpp_ggsn_ipv6_address' => '3GPP-GGSN-IPv6-Address',
- '3gpp_ggsn_mcc_mnc' => '3GPP-GGSN-MCC-MNC',
- '3gpp_gprs_negotiated_qos' => '3GPP-GPRS-Negotiated-QoS-profile',
- '3gpp_imsi' => '3GPP-IMSI',
- '3gpp_imsi_mcc_mnc' => '3GPP-IMSI-MCC-MNC',
- '3gpp_ipv6_dns_servers' => '3GPP-IPv6-DNS-Servers',
- '3gpp_nsapi' => '3GPP-NSAPI',
- '3gpp_pdp_type' => '3GPP-PDP-Type',
- '3gpp_selection_mode' => '3GPP-Selection-Mode',
- '3gpp_session_stop_indica' => '3GPP-Session-Stop-Indicator',
- '3gpp_sgsn_address' => '3GPP-SGSN-Address',
- '3gpp_sgsn_ipv6_address' => '3GPP-SGSN-IPv6-Address',
- 'aat_assign_ip_pool' => 'AAT-Assign-IP-Pool',
- 'aat_atm_direct' => 'AAT-ATM-Direct',
- 'aat_atm_traffic_profile' => 'AAT-ATM-Traffic-Profile',
- 'aat_atm_vci' => 'AAT-ATM-VCI',
- 'aat_atm_vpi' => 'AAT-ATM-VPI',
- 'aat_client_primary_dns' => 'AAT-Client-Primary-DNS',
- 'aat_client_primary_wins_' => 'AAT-Client-Primary-WINS-NBNS',
- 'aat_client_secondary_win' => 'AAT-Client-Secondary-WINS-NBNS',
- 'aat_data_filter' => 'AAT-Data-Filter',
- 'aat_input_octets_diff' => 'AAT-Input-Octets-Diff',
- 'aat_ip_pool_definition' => 'AAT-IP-Pool-Definition',
- 'aat_ip_tos' => 'AAT-IP-TOS',
- 'aat_ip_tos_apply_to' => 'AAT-IP-TOS-Apply-To',
- 'aat_ip_tos_precedence' => 'AAT-IP-TOS-Precedence',
- 'aat_mcast_client' => 'AAT-MCast-Client',
- 'aat_output_octets_diff' => 'AAT-Output-Octets-Diff',
- 'aat_ppp_address' => 'AAT-PPP-Address',
- 'aat_require_auth' => 'AAT-Require-Auth',
- 'aat_source_ip_check' => 'AAT-Source-IP-Check',
- 'aat_user_mac_address' => 'AAT-User-MAC-Address',
- 'aat_vrouter_name' => 'AAT-Vrouter-Name',
- 'acc_access_community' => 'Acc-Access-Community',
- 'acc_access_partition' => 'Acc-Access-Partition',
- 'acc_acct_on_off_reason' => 'Acc-Acct-On-Off-Reason',
- 'acc_ace_token' => 'Acc-Ace-Token',
- 'acc_ace_token_ttl' => 'Acc-Ace-Token-Ttl',
- 'acc_apsm_oversubscribed' => 'Acc-Apsm-Oversubscribed',
- 'acc_bridging_support' => 'Acc-Bridging-Support',
- 'acc_callback_cbcp_type' => 'Acc-Callback-CBCP-Type',
- 'acc_callback_delay' => 'Acc-Callback-Delay',
- 'acc_callback_mode' => 'Acc-Callback-Mode',
- 'acc_callback_num_valid' => 'Acc-Callback-Num-Valid',
- 'acc_ccp_option' => 'Acc-Ccp-Option',
- 'acc_clearing_cause' => 'Acc-Clearing-Cause',
- 'acc_clearing_location' => 'Acc-Clearing-Location',
- 'acc_connect_rx_speed' => 'Acc-Connect-Rx-Speed',
- 'acc_connect_tx_speed' => 'Acc-Connect-Tx-Speed',
- 'acc_customer_id' => 'Acc-Customer-Id',
- 'acc_dial_port_index' => 'Acc-Dial-Port-Index',
- 'acc_dialout_auth_mode' => 'Acc-Dialout-Auth-Mode',
- 'acc_dialout_auth_passwor' => 'Acc-Dialout-Auth-Password',
- 'acc_dialout_auth_usernam' => 'Acc-Dialout-Auth-Username',
- 'acc_dns_server_pri' => 'Acc-Dns-Server-Pri',
- 'acc_dns_server_sec' => 'Acc-Dns-Server-Sec',
- 'acc_igmp_admin_state' => 'Acc-Igmp-Admin-State',
- 'acc_igmp_version' => 'Acc-Igmp-Version',
- 'acc_input_errors' => 'Acc-Input-Errors',
- 'acc_ip_compression' => 'Acc-Ip-Compression',
- 'acc_ip_gateway_pri' => 'Acc-Ip-Gateway-Pri',
- 'acc_ip_gateway_sec' => 'Acc-Ip-Gateway-Sec',
- 'acc_ip_pool_name' => 'Acc-Ip-Pool-Name',
- 'acc_ipx_compression' => 'Acc-Ipx-Compression',
- 'acc_ml_call_threshold' => 'Acc-ML-Call-Threshold',
- 'acc_ml_clear_threshold' => 'Acc-ML-Clear-Threshold',
- 'acc_ml_damping_factor' => 'Acc-ML-Damping-Factor',
- 'acc_ml_mlx_admin_state' => 'Acc-ML-MLX-Admin-State',
- 'acc_modem_error_protocol' => 'Acc-Modem-Error-Protocol',
- 'acc_modem_modulation_typ' => 'Acc-Modem-Modulation-Type',
- 'acc_nbns_server_pri' => 'Acc-Nbns-Server-Pri',
- 'acc_nbns_server_sec' => 'Acc-Nbns-Server-Sec',
- 'acc_output_errors' => 'Acc-Output-Errors',
- 'acc_reason_code' => 'Acc-Reason-Code',
- 'acc_request_type' => 'Acc-Request-Type',
- 'acc_route_policy' => 'Acc-Route-Policy',
- 'acc_service_profile' => 'Acc-Service-Profile',
- 'acc_tunnel_port' => 'Acc-Tunnel-Port',
- 'acc_tunnel_secret' => 'Acc-Tunnel-Secret',
- 'acc_vpsm_reject_cause' => 'Acc-Vpsm-Reject-Cause',
- 'acct_authentic' => 'Acct-Authentic',
- 'acct_delay_time' => 'Acct-Delay-Time',
- 'acct_dyn_ac_ent' => 'Acct_Dyn_Ac_Ent',
- 'acct_dyn_ac_enu' => 'Acct-Dyn-Ac-Ent',
- 'acct_input_gigawords' => 'Acct-Input-Gigawords',
- 'acct_input_octets' => 'Acct-Input-Octets',
- 'acct_input_octets_64' => 'Acct_Input_Octets_64',
- 'acct_input_octets_65' => 'Acct-Input-Octets-64',
- 'acct_input_packets' => 'Acct-Input-Packets',
- 'acct_input_packets_64' => 'Acct_Input_Packets_64',
- 'acct_input_packets_65' => 'Acct-Input-Packets-64',
- 'acct_interim_interval' => 'Acct-Interim-Interval',
- 'acct_link_count' => 'Acct-Link-Count',
- 'acct_mcast_in_octets' => 'Acct_Mcast_In_Octets',
- 'acct_mcast_in_octett' => 'Acct-Mcast-In-Octets',
- 'acct_mcast_in_packets' => 'Acct_Mcast_In_Packets',
- 'acct_mcast_in_packett' => 'Acct-Mcast-In-Packets',
- 'acct_mcast_out_octets' => 'Acct_Mcast_Out_Octets',
- 'acct_mcast_out_octett' => 'Acct-Mcast-Out-Octets',
- 'acct_mcast_out_packets' => 'Acct_Mcast_Out_Packets',
- 'acct_mcast_out_packett' => 'Acct-Mcast-Out-Packets',
- 'acct_multi_session_id' => 'Acct-Multi-Session-Id',
- 'acct_output_gigawords' => 'Acct-Output-Gigawords',
- 'acct_output_octets' => 'Acct-Output-Octets',
- 'acct_output_octets_64' => 'Acct_Output_Octets_64',
- 'acct_output_octets_65' => 'Acct-Output-Octets-64',
- 'acct_output_packets' => 'Acct-Output-Packets',
- 'acct_output_packets_64' => 'Acct_Output_Packets_64',
- 'acct_output_packets_65' => 'Acct-Output-Packets-64',
- 'acct_session_gigawords' => 'Acct-Session-Gigawords',
- 'acct_session_id' => 'Acct-Session-Id',
- 'acct_session_input_gigaw' => 'Acct-Session-Input-Gigawords',
- 'acct_session_input_octet' => 'Acct-Session-Input-Octets',
- 'acct_session_octets' => 'Acct-Session-Octets',
- 'acct_session_output_giga' => 'Acct-Session-Output-Gigawords',
- 'acct_session_output_octe' => 'Acct-Session-Output-Octets',
- 'acct_session_start_time' => 'Acct-Session-Start-Time',
- 'acct_session_time' => 'Acct-Session-Time',
- 'acct_status_type' => 'Acct-Status-Type',
- 'acct_terminate_cause' => 'Acct-Terminate-Cause',
- 'acct_tunnel_connection' => 'Acct-Tunnel-Connection',
- 'acct_tunnel_packets_lost' => 'Acct-Tunnel-Packets-Lost',
- 'acct_type' => 'Acct-Type',
- 'acct_unique_session_id' => 'Acct-Unique-Session-Id',
- 'add_prefix' => 'Add-Prefix',
- 'add_suffix' => 'Add-Suffix',
- 'alteon_service_type' => 'Alteon-Service-Type',
- 'altiga_access_hours_g_u' => 'Altiga-Access-Hours-G/U',
- 'altiga_allow_alpha_only_' => 'Altiga-Allow-Alpha-Only-Passwords-G',
- 'altiga_ipsec_allow_passw' => 'Altiga-IPSec-Allow-Passwd-Store-G/U',
- 'altiga_ipsec_authenticat' => 'Altiga-IPSec-Authentication-G',
- 'altiga_ipsec_banner_g' => 'Altiga-IPSec-Banner-G',
- 'altiga_ipsec_default_dom' => 'Altiga-IPSec-Default-Domain-G',
- 'altiga_ipsec_l2l_keepali' => 'Altiga-IPSec-L2L-Keepalives-G',
- 'altiga_ipsec_mode_config' => 'Altiga-IPSec-Mode-Config-G',
- 'altiga_ipsec_over_nat_g' => 'Altiga-IPSec-Over-NAT-G',
- 'altiga_ipsec_over_nat_po' => 'Altiga-IPSec-Over-NAT-Port-Num-G',
- 'altiga_ipsec_sec_associa' => 'Altiga-IPSec-Sec-Association-G/U',
- 'altiga_ipsec_secondary_d' => 'Altiga-IPSec-Secondary-Domains-G',
- 'altiga_ipsec_split_tunne' => 'Altiga-IPSec-Split-Tunnel-List-G',
- 'altiga_ipsec_tunnel_type' => 'Altiga-IPSec-Tunnel-Type-G',
- 'altiga_ipsec_user_group_' => 'Altiga-IPSec-User-Group-Lock-G',
- 'altiga_l2tp_encryption_g' => 'Altiga-L2TP-Encryption-G',
- 'altiga_l2tp_min_authenti' => 'Altiga-L2TP-Min-Authentication-G/U',
- 'altiga_min_password_leng' => 'Altiga-Min-Password-Length-G',
- 'altiga_pptp_encryption_g' => 'Altiga-PPTP-Encryption-G',
- 'altiga_pptp_min_authenti' => 'Altiga-PPTP-Min-Authentication-G/U',
- 'altiga_primary_dns_g' => 'Altiga-Primary-DNS-G',
- 'altiga_primary_wins_g' => 'Altiga-Primary-WINS-G',
- 'altiga_priority_on_sep_g' => 'Altiga-Priority-on-SEP-G/U',
- 'altiga_secondary_dns_g' => 'Altiga-Secondary-DNS-G',
- 'altiga_secondary_wins_g' => 'Altiga-Secondary-WINS-G',
- 'altiga_sep_card_assignme' => 'Altiga-SEP-Card-Assignment-G/U',
- 'altiga_simultaneous_logi' => 'Altiga-Simultaneous-Logins-G/U',
- 'altiga_tunneling_protoco' => 'Altiga-Tunneling-Protocols-G/U',
- 'altiga_use_client_addres' => 'Altiga-Use-Client-Address-G/U',
- 'annex_acct_servers' => 'Annex-Acct-Servers',
- 'annex_addr_resolution_pr' => 'Annex-Addr-Resolution-Protocol',
- 'annex_addr_resolution_se' => 'Annex-Addr-Resolution-Servers',
- 'annex_audit_level' => 'Annex-Audit-Level',
- 'annex_authen_servers' => 'Annex-Authen-Servers',
- 'annex_begin_modulation' => 'Annex-Begin-Modulation',
- 'annex_begin_receive_line' => 'Annex-Begin-Receive-Line-Level',
- 'annex_callback_portlist' => 'Annex-Callback-Portlist',
- 'annex_cli_command' => 'Annex-CLI-Command',
- 'annex_cli_filter' => 'Annex-CLI-Filter',
- 'annex_compression_protoc' => 'Annex-Compression-Protocol',
- 'annex_connect_progress' => 'Annex-Connect-Progress',
- 'annex_disconnect_reason' => 'Annex-Disconnect-Reason',
- 'annex_domain_name' => 'Annex-Domain-Name',
- 'annex_edo' => 'Annex-EDO',
- 'annex_end_modulation' => 'Annex-End-Modulation',
- 'annex_end_receive_line_l' => 'Annex-End-Receive-Line-Level',
- 'annex_error_correction_p' => 'Annex-Error-Correction-Prot',
- 'annex_filter' => 'Annex-Filter',
- 'annex_host_allow' => 'Annex-Host-Allow',
- 'annex_host_restrict' => 'Annex-Host-Restrict',
- 'annex_input_filter' => 'Annex-Input-Filter',
- 'annex_keypress_timeout' => 'Annex-Keypress-Timeout',
- 'annex_local_ip_address' => 'Annex-Local-IP-Address',
- 'annex_local_username' => 'Annex-Local-Username',
- 'annex_logical_channel_nu' => 'Annex-Logical-Channel-Number',
- 'annex_maximum_call_durat' => 'Annex-Maximum-Call-Duration',
- 'annex_modem_disc_reason' => 'Annex-Modem-Disc-Reason',
- 'annex_mrru' => 'Annex-MRRU',
- 'annex_multicast_rate_lim' => 'Annex-Multicast-Rate-Limit',
- 'annex_multilink_id' => 'Annex-Multilink-Id',
- 'annex_num_in_multilink' => 'Annex-Num-In-Multilink',
- 'annex_output_filter' => 'Annex-Output-Filter',
- 'annex_pool_id' => 'Annex-Pool-Id',
- 'annex_port' => 'Annex-Port',
- 'annex_ppp_trace_level' => 'Annex-PPP-Trace-Level',
- 'annex_pre_input_octets' => 'Annex-Pre-Input-Octets',
- 'annex_pre_input_packets' => 'Annex-Pre-Input-Packets',
- 'annex_pre_output_octets' => 'Annex-Pre-Output-Octets',
- 'annex_pre_output_packets' => 'Annex-Pre-Output-Packets',
- 'annex_primary_dns_server' => 'Annex-Primary-DNS-Server',
- 'annex_primary_nbns_serve' => 'Annex-Primary-NBNS-Server',
- 'annex_product_name' => 'Annex-Product-Name',
- 'annex_rate_reneg_req_rcv' => 'Annex-Rate-Reneg-Req-Rcvd',
- 'annex_rate_reneg_req_sen' => 'Annex-Rate-Reneg-Req-Sent',
- 'annex_re_chap_timeout' => 'Annex-Re-CHAP-Timeout',
- 'annex_receive_speed' => 'Annex-Receive-Speed',
- 'annex_retrain_requests_r' => 'Annex-Retrain-Requests-Rcvd',
- 'annex_retrain_requests_s' => 'Annex-Retrain-Requests-Sent',
- 'annex_retransmitted_pack' => 'Annex-Retransmitted-Packets',
- 'annex_sec_profile_index' => 'Annex-Sec-Profile-Index',
- 'annex_secondary_dns_serv' => 'Annex-Secondary-DNS-Server',
- 'annex_secondary_nbns_ser' => 'Annex-Secondary-NBNS-Server',
- 'annex_signal_to_noise_ra' => 'Annex-Signal-to-Noise-Ratio',
- 'annex_sw_version' => 'Annex-SW-Version',
- 'annex_syslog_tap' => 'Annex-Syslog-Tap',
- 'annex_system_disc_reason' => 'Annex-System-Disc-Reason',
- 'annex_transmit_speed' => 'Annex-Transmit-Speed',
- 'annex_transmitted_packet' => 'Annex-Transmitted-Packets',
- 'annex_tunnel_authen_mode' => 'Annex-Tunnel-Authen-Mode',
- 'annex_tunnel_authen_type' => 'Annex-Tunnel-Authen-Type',
- 'annex_unauthenticated_ti' => 'Annex-Unauthenticated-Time',
- 'annex_user_level' => 'Annex-User-Level',
- 'annex_user_server_locati' => 'Annex-User-Server-Location',
- 'annex_wan_number' => 'Annex-Wan-Number',
- 'arap_challenge_response' => 'ARAP-Challenge-Response',
- 'arap_features' => 'ARAP-Features',
- 'arap_password' => 'ARAP-Password',
- 'arap_security' => 'ARAP-Security',
- 'arap_security_data' => 'ARAP-Security-Data',
- 'arap_zone_access' => 'ARAP-Zone-Access',
- 'ascend_access_intercept_' => 'Ascend-Access-Intercept-LEA',
- 'ascend_access_intercepta' => 'Ascend-Access-Intercept-Log',
- 'ascend_add_seconds' => 'Ascend-Add-Seconds',
- 'ascend_appletalk_peer_mo' => 'Ascend-Appletalk-Peer-Mode',
- 'ascend_appletalk_route' => 'Ascend-Appletalk-Route',
- 'ascend_ara_pw' => 'Ascend-Ara-PW',
- 'ascend_assign_ip_client' => 'Ascend-Assign-IP-Client',
- 'ascend_assign_ip_global_' => 'Ascend-Assign-IP-Global-Pool',
- 'ascend_assign_ip_pool' => 'Ascend-Assign-IP-Pool',
- 'ascend_assign_ip_server' => 'Ascend-Assign-IP-Server',
- 'ascend_atm_connect_group' => 'Ascend-ATM-Connect-Group',
- 'ascend_atm_connect_vci' => 'Ascend-ATM-Connect-Vci',
- 'ascend_atm_connect_vpi' => 'Ascend-ATM-Connect-Vpi',
- 'ascend_atm_direct' => 'Ascend-ATM-Direct',
- 'ascend_atm_direct_profil' => 'Ascend-ATM-Direct-Profile',
- 'ascend_atm_fault_managem' => 'Ascend-ATM-Fault-Management',
- 'ascend_atm_group' => 'Ascend-ATM-Group',
- 'ascend_atm_loopback_cell' => 'Ascend-ATM-Loopback-Cell-Loss',
- 'ascend_atm_vci' => 'Ascend-ATM-Vci',
- 'ascend_atm_vpi' => 'Ascend-ATM-Vpi',
- 'ascend_auth_delay' => 'Ascend-Auth-Delay',
- 'ascend_auth_type' => 'Ascend-Auth-Type',
- 'ascend_authen_alias' => 'Ascend-Authen-Alias',
- 'ascend_backup' => 'Ascend-Backup',
- 'ascend_bacp_enable' => 'Ascend-BACP-Enable',
- 'ascend_base_channel_coun' => 'Ascend-Base-Channel-Count',
- 'ascend_bi_directional_au' => 'Ascend-Bi-Directional-Auth',
- 'ascend_billing_number' => 'Ascend-Billing-Number',
- 'ascend_bir_bridge_group' => 'Ascend-BIR-Bridge-Group',
- 'ascend_bir_enable' => 'Ascend-BIR-Enable',
- 'ascend_bir_proxy' => 'Ascend-BIR-Proxy',
- 'ascend_bridge' => 'Ascend-Bridge',
- 'ascend_bridge_address' => 'Ascend-Bridge-Address',
- 'ascend_bridge_non_pppoe' => 'Ascend-Bridge-Non-PPPoE',
- 'ascend_cache_refresh' => 'Ascend-Cache-Refresh',
- 'ascend_cache_time' => 'Ascend-Cache-Time',
- 'ascend_call_attempt_limi' => 'Ascend-Call-Attempt-Limit',
- 'ascend_call_block_durati' => 'Ascend-Call-Block-Duration',
- 'ascend_call_by_call' => 'Ascend-Call-By-Call',
- 'ascend_call_direction' => 'Ascend-Call-Direction',
- 'ascend_call_filter' => 'Ascend-Call-Filter',
- 'ascend_call_type' => 'Ascend-Call-Type',
- 'ascend_callback' => 'Ascend-Callback',
- 'ascend_callback_delay' => 'Ascend-Callback-Delay',
- 'ascend_calling_id_number' => 'Ascend-Calling-Id-Number-Plan',
- 'ascend_calling_id_presen' => 'Ascend-Calling-Id-Presentatn',
- 'ascend_calling_id_screen' => 'Ascend-Calling-Id-Screening',
- 'ascend_calling_id_type_o' => 'Ascend-Calling-Id-Type-Of-Num',
- 'ascend_calling_subaddres' => 'Ascend-Calling-Subaddress',
- 'ascend_cbcp_delay' => 'Ascend-CBCP-Delay',
- 'ascend_cbcp_enable' => 'Ascend-CBCP-Enable',
- 'ascend_cbcp_mode' => 'Ascend-CBCP-Mode',
- 'ascend_cbcp_trunk_group' => 'Ascend-CBCP-Trunk-Group',
- 'ascend_cir_timer' => 'Ascend-CIR-Timer',
- 'ascend_ckt_type' => 'Ascend-Ckt-Type',
- 'ascend_client_assign_dns' => 'Ascend-Client-Assign-DNS',
- 'ascend_client_assign_win' => 'Ascend-Client-Assign-WINS',
- 'ascend_client_gateway' => 'Ascend-Client-Gateway',
- 'ascend_client_primary_dn' => 'Ascend-Client-Primary-DNS',
- 'ascend_client_primary_wi' => 'Ascend-Client-Primary-WINS',
- 'ascend_client_secondary_' => 'Ascend-Client-Secondary-WINS',
- 'ascend_client_secondarya' => 'Ascend-Client-Secondary-DNS',
- 'ascend_connect_progress' => 'Ascend-Connect-Progress',
- 'ascend_data_filter' => 'Ascend-Data-Filter',
- 'ascend_data_rate' => 'Ascend-Data-Rate',
- 'ascend_data_svc' => 'Ascend-Data-Svc',
- 'ascend_dba_monitor' => 'Ascend-DBA-Monitor',
- 'ascend_dec_channel_count' => 'Ascend-Dec-Channel-Count',
- 'ascend_destination_nas_p' => 'Ascend-Destination-Nas-Port',
- 'ascend_dhcp_maximum_leas' => 'Ascend-DHCP-Maximum-Leases',
- 'ascend_dhcp_pool_number' => 'Ascend-DHCP-Pool-Number',
- 'ascend_dhcp_reply' => 'Ascend-DHCP-Reply',
- 'ascend_dial_number' => 'Ascend-Dial-Number',
- 'ascend_dialed_number' => 'Ascend-Dialed-Number',
- 'ascend_dialout_allowed' => 'Ascend-Dialout-Allowed',
- 'ascend_disconnect_cause' => 'Ascend-Disconnect-Cause',
- 'ascend_dropped_octets' => 'Ascend-Dropped-Octets',
- 'ascend_dropped_packets' => 'Ascend-Dropped-Packets',
- 'ascend_dsl_cir_recv_limi' => 'Ascend-Dsl-CIR-Recv-Limit',
- 'ascend_dsl_cir_xmit_limi' => 'Ascend-Dsl-CIR-Xmit-Limit',
- 'ascend_dsl_downstream_li' => 'Ascend-Dsl-Downstream-Limit',
- 'ascend_dsl_rate_mode' => 'Ascend-Dsl-Rate-Mode',
- 'ascend_dsl_rate_type' => 'Ascend-Dsl-Rate-Type',
- 'ascend_dsl_upstream_limi' => 'Ascend-Dsl-Upstream-Limit',
- 'ascend_egress_enabled' => 'Ascend-Egress-Enabled',
- 'ascend_endpoint_disc' => 'Ascend-Endpoint-Disc',
- 'ascend_event_type' => 'Ascend-Event-Type',
- 'ascend_expect_callback' => 'Ascend-Expect-Callback',
- 'ascend_fcp_parameter' => 'Ascend-FCP-Parameter',
- 'ascend_filter' => 'Ascend-Filter',
- 'ascend_filter_required' => 'Ascend-Filter-Required',
- 'ascend_first_dest' => 'Ascend-First-Dest',
- 'ascend_force_56' => 'Ascend-Force-56',
- 'ascend_fr_08_mode' => 'Ascend-FR-08-Mode',
- 'ascend_fr_circuit_name' => 'Ascend-FR-Circuit-Name',
- 'ascend_fr_dce_n392' => 'Ascend-FR-DCE-N392',
- 'ascend_fr_dce_n393' => 'Ascend-FR-DCE-N393',
- 'ascend_fr_direct' => 'Ascend-FR-Direct',
- 'ascend_fr_direct_dlci' => 'Ascend-FR-Direct-DLCI',
- 'ascend_fr_direct_profile' => 'Ascend-FR-Direct-Profile',
- 'ascend_fr_dlci' => 'Ascend-FR-DLCI',
- 'ascend_fr_dte_n392' => 'Ascend-FR-DTE-N392',
- 'ascend_fr_dte_n393' => 'Ascend-FR-DTE-N393',
- 'ascend_fr_link_mgt' => 'Ascend-FR-Link-Mgt',
- 'ascend_fr_link_status_dl' => 'Ascend-FR-Link-Status-DLCI',
- 'ascend_fr_linkup' => 'Ascend-FR-LinkUp',
- 'ascend_fr_n391' => 'Ascend-FR-N391',
- 'ascend_fr_nailed_grp' => 'Ascend-FR-Nailed-Grp',
- 'ascend_fr_profile_name' => 'Ascend-FR-Profile-Name',
- 'ascend_fr_svc_addr' => 'Ascend-FR-SVC-Addr',
- 'ascend_fr_t391' => 'Ascend-FR-T391',
- 'ascend_fr_t392' => 'Ascend-FR-T392',
- 'ascend_fr_type' => 'Ascend-FR-Type',
- 'ascend_ft1_caller' => 'Ascend-FT1-Caller',
- 'ascend_global_call_id' => 'Ascend-Global-Call-Id',
- 'ascend_group' => 'Ascend-Group',
- 'ascend_h323_conference_i' => 'Ascend-H323-Conference-Id',
- 'ascend_h323_dialed_time' => 'Ascend-H323-Dialed-Time',
- 'ascend_h323_fegw_address' => 'Ascend-H323-Fegw-Address',
- 'ascend_h323_gatekeeper' => 'Ascend-H323-Gatekeeper',
- 'ascend_handle_ipx' => 'Ascend-Handle-IPX',
- 'ascend_history_weigh_typ' => 'Ascend-History-Weigh-Type',
- 'ascend_home_agent_ip_add' => 'Ascend-Home-Agent-IP-Addr',
- 'ascend_home_agent_passwo' => 'Ascend-Home-Agent-Password',
- 'ascend_home_agent_udp_po' => 'Ascend-Home-Agent-UDP-Port',
- 'ascend_home_network_name' => 'Ascend-Home-Network-Name',
- 'ascend_host_info' => 'Ascend-Host-Info',
- 'ascend_idle_limit' => 'Ascend-Idle-Limit',
- 'ascend_if_netmask' => 'Ascend-IF-Netmask',
- 'ascend_inc_channel_count' => 'Ascend-Inc-Channel-Count',
- 'ascend_inter_arrival_jit' => 'Ascend-Inter-Arrival-Jitter',
- 'ascend_ip_direct' => 'Ascend-IP-Direct',
- 'ascend_ip_pool_chaining' => 'Ascend-IP-Pool-Chaining',
- 'ascend_ip_pool_definitio' => 'Ascend-IP-Pool-Definition',
- 'ascend_ip_tos' => 'Ascend-IP-TOS',
- 'ascend_ip_tos_apply_to' => 'Ascend-IP-TOS-Apply-To',
- 'ascend_ip_tos_precedence' => 'Ascend-IP-TOS-Precedence',
- 'ascend_ipsec_profile' => 'Ascend-IPSEC-Profile',
- 'ascend_ipx_alias' => 'Ascend-IPX-Alias',
- 'ascend_ipx_header_compre' => 'Ascend-IPX-Header-Compression',
- 'ascend_ipx_node_addr' => 'Ascend-IPX-Node-Addr',
- 'ascend_ipx_peer_mode' => 'Ascend-IPX-Peer-Mode',
- 'ascend_ipx_route' => 'Ascend-IPX-Route',
- 'ascend_link_compression' => 'Ascend-Link-Compression',
- 'ascend_max_shared_users' => 'Ascend-Max-Shared-Users',
- 'ascend_maximum_call_dura' => 'Ascend-Maximum-Call-Duration',
- 'ascend_maximum_channels' => 'Ascend-Maximum-Channels',
- 'ascend_maximum_time' => 'Ascend-Maximum-Time',
- 'ascend_menu_item' => 'Ascend-Menu-Item',
- 'ascend_menu_selector' => 'Ascend-Menu-Selector',
- 'ascend_metric' => 'Ascend-Metric',
- 'ascend_minimum_channels' => 'Ascend-Minimum-Channels',
- 'ascend_modem_portno' => 'Ascend-Modem-PortNo',
- 'ascend_modem_shelfno' => 'Ascend-Modem-ShelfNo',
- 'ascend_modem_slotno' => 'Ascend-Modem-SlotNo',
- 'ascend_mpp_idle_percent' => 'Ascend-MPP-Idle-Percent',
- 'ascend_mtu' => 'Ascend-MTU',
- 'ascend_multicast_client' => 'Ascend-Multicast-Client',
- 'ascend_multicast_gleave_' => 'Ascend-Multicast-GLeave-Delay',
- 'ascend_multicast_rate_li' => 'Ascend-Multicast-Rate-Limit',
- 'ascend_multilink_id' => 'Ascend-Multilink-ID',
- 'ascend_nas_port_format' => 'Ascend-NAS-Port-Format',
- 'ascend_netware_timeout' => 'Ascend-Netware-timeout',
- 'ascend_num_in_multilink' => 'Ascend-Num-In-Multilink',
- 'ascend_number_sessions' => 'Ascend-Number-Sessions',
- 'ascend_numbering_plan_id' => 'Ascend-Numbering-Plan-ID',
- 'ascend_owner_ip_addr' => 'Ascend-Owner-IP-Addr',
- 'ascend_port_redir_portnu' => 'Ascend-Port-Redir-Portnum',
- 'ascend_port_redir_protoc' => 'Ascend-Port-Redir-Protocol',
- 'ascend_port_redir_server' => 'Ascend-Port-Redir-Server',
- 'ascend_ppp_address' => 'Ascend-PPP-Address',
- 'ascend_ppp_async_map' => 'Ascend-PPP-Async-Map',
- 'ascend_ppp_vj_1172' => 'Ascend-PPP-VJ-1172',
- 'ascend_ppp_vj_slot_comp' => 'Ascend-PPP-VJ-Slot-Comp',
- 'ascend_pppoe_enable' => 'Ascend-PPPoE-Enable',
- 'ascend_pre_input_octets' => 'Ascend-Pre-Input-Octets',
- 'ascend_pre_input_packets' => 'Ascend-Pre-Input-Packets',
- 'ascend_pre_output_octets' => 'Ascend-Pre-Output-Octets',
- 'ascend_pre_output_packet' => 'Ascend-Pre-Output-Packets',
- 'ascend_preempt_limit' => 'Ascend-Preempt-Limit',
- 'ascend_presession_time' => 'Ascend-PreSession-Time',
- 'ascend_pri_number_type' => 'Ascend-PRI-Number-Type',
- 'ascend_primary_home_agen' => 'Ascend-Primary-Home-Agent',
- 'ascend_private_route' => 'Ascend-Private-Route',
- 'ascend_private_route_req' => 'Ascend-Private-Route-Required',
- 'ascend_private_route_tab' => 'Ascend-Private-Route-Table-ID',
- 'ascend_pw_lifetime' => 'Ascend-PW-Lifetime',
- 'ascend_pw_warntime' => 'Ascend-PW-Warntime',
- 'ascend_qos_downstream' => 'Ascend-QOS-Downstream',
- 'ascend_qos_upstream' => 'Ascend-QOS-Upstream',
- 'ascend_receive_secret' => 'Ascend-Receive-Secret',
- 'ascend_recv_name' => 'Ascend-Recv-Name',
- 'ascend_redirect_number' => 'Ascend-Redirect-Number',
- 'ascend_remote_addr' => 'Ascend-Remote-Addr',
- 'ascend_remote_fw' => 'Ascend-Remote-FW',
- 'ascend_remove_seconds' => 'Ascend-Remove-Seconds',
- 'ascend_require_auth' => 'Ascend-Require-Auth',
- 'ascend_route_appletalk' => 'Ascend-Route-Appletalk',
- 'ascend_route_ip' => 'Ascend-Route-IP',
- 'ascend_route_ipx' => 'Ascend-Route-IPX',
- 'ascend_secondary_home_ag' => 'Ascend-Secondary-Home-Agent',
- 'ascend_seconds_of_histor' => 'Ascend-Seconds-Of-History',
- 'ascend_send_auth' => 'Ascend-Send-Auth',
- 'ascend_send_passwd' => 'Ascend-Send-Passwd',
- 'ascend_send_secret' => 'Ascend-Send-Secret',
- 'ascend_service_type' => 'Ascend-Service-Type',
- 'ascend_session_svr_key' => 'Ascend-Session-Svr-Key',
- 'ascend_session_type' => 'Ascend-Session-Type',
- 'ascend_shared_profile_en' => 'Ascend-Shared-Profile-Enable',
- 'ascend_source_auth' => 'Ascend-Source-Auth',
- 'ascend_source_ip_check' => 'Ascend-Source-IP-Check',
- 'ascend_svc_enabled' => 'Ascend-SVC-Enabled',
- 'ascend_target_util' => 'Ascend-Target-Util',
- 'ascend_telnet_profile' => 'Ascend-Telnet-Profile',
- 'ascend_temporary_rtes' => 'Ascend-Temporary-Rtes',
- 'ascend_third_prompt' => 'Ascend-Third-Prompt',
- 'ascend_token_expiry' => 'Ascend-Token-Expiry',
- 'ascend_token_idle' => 'Ascend-Token-Idle',
- 'ascend_token_immediate' => 'Ascend-Token-Immediate',
- 'ascend_traffic_shaper' => 'Ascend-Traffic-Shaper',
- 'ascend_transit_number' => 'Ascend-Transit-Number',
- 'ascend_ts_idle_limit' => 'Ascend-TS-Idle-Limit',
- 'ascend_ts_idle_mode' => 'Ascend-TS-Idle-Mode',
- 'ascend_tunnel_vrouter_na' => 'Ascend-Tunnel-VRouter-Name',
- 'ascend_tunneling_protoco' => 'Ascend-Tunneling-Protocol',
- 'ascend_user_acct_base' => 'Ascend-User-Acct-Base',
- 'ascend_user_acct_host' => 'Ascend-User-Acct-Host',
- 'ascend_user_acct_key' => 'Ascend-User-Acct-Key',
- 'ascend_user_acct_port' => 'Ascend-User-Acct-Port',
- 'ascend_user_acct_time' => 'Ascend-User-Acct-Time',
- 'ascend_user_acct_type' => 'Ascend-User-Acct-Type',
- 'ascend_uu_info' => 'Ascend-UU-Info',
- 'ascend_vrouter_name' => 'Ascend-VRouter-Name',
- 'ascend_x25_cug' => 'Ascend-X25-Cug',
- 'ascend_x25_nui' => 'Ascend-X25-Nui',
- 'ascend_x25_nui_password_' => 'Ascend-X25-Nui-Password-Prompt',
- 'ascend_x25_nui_prompt' => 'Ascend-X25-Nui-Prompt',
- 'ascend_x25_pad_alias_1' => 'Ascend-X25-Pad-Alias-1',
- 'ascend_x25_pad_alias_2' => 'Ascend-X25-Pad-Alias-2',
- 'ascend_x25_pad_alias_3' => 'Ascend-X25-Pad-Alias-3',
- 'ascend_x25_pad_banner' => 'Ascend-X25-Pad-Banner',
- 'ascend_x25_pad_prompt' => 'Ascend-X25-Pad-Prompt',
- 'ascend_x25_pad_x3_parame' => 'Ascend-X25-Pad-X3-Parameters',
- 'ascend_x25_pad_x3_profil' => 'Ascend-X25-Pad-X3-Profile',
- 'ascend_x25_profile_name' => 'Ascend-X25-Profile-Name',
- 'ascend_x25_reverse_charg' => 'Ascend-X25-Reverse-Charging',
- 'ascend_x25_rpoa' => 'Ascend-X25-Rpoa',
- 'ascend_x25_x121_address' => 'Ascend-X25-X121-Address',
- 'ascend_xmit_rate' => 'Ascend-Xmit-Rate',
- 'assigned_ip_address' => 'Assigned_IP_Address',
- 'assigned_ip_addrest' => 'Assigned-IP-Address',
- 'auth_type' => 'Auth-Type',
- 'autz_type' => 'Autz-Type',
- 'bg_aging_time' => 'BG_Aging_Time',
- 'bg_aging_timf' => 'BG-Aging-Time',
- 'bg_path_cost' => 'BG_Path_Cost',
- 'bg_path_cosu' => 'BG-Path-Cost',
- 'bg_span_dis' => 'BG_Span_Dis',
- 'bg_span_dit' => 'BG-Span-Dis',
- 'bg_trans_bpdu' => 'BG_Trans_BPDU',
- 'bg_trans_bpdv' => 'BG-Trans-BPDU',
- 'bind_auth_context' => 'Bind_Auth_Context',
- 'bind_auth_contexu' => 'Bind-Auth-Context',
- 'bind_auth_max_sessions' => 'Bind_Auth_Max_Sessions',
- 'bind_auth_max_sessiont' => 'Bind-Auth-Max-Sessions',
- 'bind_auth_protocol' => 'Bind_Auth_Protocol',
- 'bind_auth_protocom' => 'Bind-Auth-Protocol',
- 'bind_auth_service_grp' => 'Bind_Auth_Service_Grp',
- 'bind_auth_service_grq' => 'Bind-Auth-Service-Grp',
- 'bind_bypass_bypass' => 'Bind_Bypass_Bypass',
- 'bind_bypass_bypast' => 'Bind-Bypass-Bypass',
- 'bind_bypass_context' => 'Bind_Bypass_Context',
- 'bind_bypass_contexu' => 'Bind-Bypass-Context',
- 'bind_dot1q_port' => 'Bind_Dot1q_Port',
- 'bind_dot1q_poru' => 'Bind-Dot1q-Port',
- 'bind_dot1q_slot' => 'Bind_Dot1q_Slot',
- 'bind_dot1q_slou' => 'Bind-Dot1q-Slot',
- 'bind_dot1q_vlan_tag_id' => 'Bind_Dot1q_Vlan_Tag_Id',
- 'bind_dot1q_vlan_tag_ie' => 'Bind-Dot1q-Vlan-Tag-Id',
- 'bind_int_context' => 'Bind_Int_Context',
- 'bind_int_contexu' => 'Bind-Int-Context',
- 'bind_int_interface_name' => 'Bind_Int_Interface_Name',
- 'bind_int_interface_namf' => 'Bind-Int-Interface-Name',
- 'bind_l2tp_flow_control' => 'Bind_L2TP_Flow_Control',
- 'bind_l2tp_flow_controm' => 'Bind-L2TP-Flow-Control',
- 'bind_l2tp_tunnel_name' => 'Bind_L2TP_Tunnel_Name',
- 'bind_l2tp_tunnel_namf' => 'Bind-L2TP-Tunnel-Name',
- 'bind_ses_context' => 'Bind_Ses_Context',
- 'bind_ses_contexu' => 'Bind-Ses-Context',
- 'bind_sub_password' => 'Bind_Sub_Password',
- 'bind_sub_passwore' => 'Bind-Sub-Password',
- 'bind_sub_user_at_context' => 'Bind_Sub_User_At_Context',
- 'bind_sub_user_at_contexu' => 'Bind-Sub-User-At-Context',
- 'bind_tun_context' => 'Bind_Tun_Context',
- 'bind_tun_contexu' => 'Bind-Tun-Context',
- 'bind_type' => 'Bind_Type',
- 'bind_typf' => 'Bind-Type',
- 'bintec_bibodialtable' => 'BinTec-biboDialTable',
- 'bintec_biboppptable' => 'BinTec-biboPPPTable',
- 'bintec_ipextiftable' => 'BinTec-ipExtIfTable',
- 'bintec_ipextrttable' => 'BinTec-ipExtRtTable',
- 'bintec_ipfiltertable' => 'BinTec-ipFilterTable',
- 'bintec_ipnatpresettable' => 'BinTec-ipNatPresetTable',
- 'bintec_ipqostable' => 'BinTec-ipQoSTable',
- 'bintec_iproutetable' => 'BinTec-ipRouteTable',
- 'bintec_ipxcirctable' => 'BinTec-ipxCircTable',
- 'bintec_ipxstaticroutetab' => 'BinTec-ipxStaticRouteTable',
- 'bintec_ipxstaticservtabl' => 'BinTec-ipxStaticServTable',
- 'bintec_ospfiftable' => 'BinTec-ospfIfTable',
- 'bintec_pppextiftable' => 'BinTec-pppExtIfTable',
- 'bintec_qosiftable' => 'BinTec-qosIfTable',
- 'bintec_qospolicytable' => 'BinTec-qosPolicyTable',
- 'bintec_ripcirctable' => 'BinTec-ripCircTable',
- 'bintec_sapcirctable' => 'BinTec-sapCircTable',
- 'bridge_group' => 'Bridge_Group',
- 'bridge_grouq' => 'Bridge-Group',
- 'cabletron_protocol_calla' => 'Cabletron-Protocol-Callable',
- 'cabletron_protocol_enabl' => 'Cabletron-Protocol-Enable',
- 'call_id' => 'call-id',
- 'callback_id' => 'Callback-Id',
- 'callback_number' => 'Callback-Number',
- 'called_station_id' => 'Called-Station-Id',
- 'caller_id' => 'Caller-ID',
- 'calling_station_id' => 'Calling-Station-Id',
- 'cbbsm_bandwidth' => 'CBBSM-Bandwidth',
- 'challenge_state' => 'Challenge-State',
- 'chap_challenge' => 'CHAP-Challenge',
- 'chap_password' => 'CHAP-Password',
- 'char_noecho' => 'Char-Noecho',
- 'cisco_abort_cause' => 'Cisco-Abort-Cause',
- 'cisco_account_info' => 'Cisco-Account-Info',
- 'cisco_assign_ip_pool' => 'Cisco-Assign-IP-Pool',
- 'cisco_avpair' => 'Cisco-AVPair',
- 'cisco_call_filter' => 'Cisco-Call-Filter',
- 'cisco_call_type' => 'Cisco-Call-Type',
- 'cisco_command_code' => 'Cisco-Command-Code',
- 'cisco_control_info' => 'Cisco-Control-Info',
- 'cisco_data_filter' => 'Cisco-Data-Filter',
- 'cisco_data_rate' => 'Cisco-Data-Rate',
- 'cisco_disconnect_cause' => 'Cisco-Disconnect-Cause',
- 'cisco_email_server_ack_f' => 'Cisco-Email-Server-Ack-Flag',
- 'cisco_email_server_addre' => 'Cisco-Email-Server-Address',
- 'cisco_fax_account_id_ori' => 'Cisco-Fax-Account-Id-Origin',
- 'cisco_fax_auth_status' => 'Cisco-Fax-Auth-Status',
- 'cisco_fax_connect_speed' => 'Cisco-Fax-Connect-Speed',
- 'cisco_fax_coverpage_flag' => 'Cisco-Fax-Coverpage-Flag',
- 'cisco_fax_dsn_address' => 'Cisco-Fax-Dsn-Address',
- 'cisco_fax_dsn_flag' => 'Cisco-Fax-Dsn-Flag',
- 'cisco_fax_mdn_address' => 'Cisco-Fax-Mdn-Address',
- 'cisco_fax_mdn_flag' => 'Cisco-Fax-Mdn-Flag',
- 'cisco_fax_modem_time' => 'Cisco-Fax-Modem-Time',
- 'cisco_fax_msg_id' => 'Cisco-Fax-Msg-Id',
- 'cisco_fax_pages' => 'Cisco-Fax-Pages',
- 'cisco_fax_process_abort_' => 'Cisco-Fax-Process-Abort-Flag',
- 'cisco_fax_recipient_coun' => 'Cisco-Fax-Recipient-Count',
- 'cisco_gateway_id' => 'Cisco-Gateway-Id',
- 'cisco_idle_limit' => 'Cisco-Idle-Limit',
- 'cisco_ip_direct' => 'Cisco-IP-Direct',
- 'cisco_ip_pool_definition' => 'Cisco-IP-Pool-Definition',
- 'cisco_link_compression' => 'Cisco-Link-Compression',
- 'cisco_maximum_channels' => 'Cisco-Maximum-Channels',
- 'cisco_maximum_time' => 'Cisco-Maximum-Time',
- 'cisco_multilink_id' => 'Cisco-Multilink-ID',
- 'cisco_nas_port' => 'Cisco-NAS-Port',
- 'cisco_num_in_multilink' => 'Cisco-Num-In-Multilink',
- 'cisco_port_used' => 'Cisco-Port-Used',
- 'cisco_ppp_async_map' => 'Cisco-PPP-Async-Map',
- 'cisco_ppp_vj_slot_comp' => 'Cisco-PPP-VJ-Slot-Comp',
- 'cisco_pre_input_octets' => 'Cisco-Pre-Input-Octets',
- 'cisco_pre_input_packets' => 'Cisco-Pre-Input-Packets',
- 'cisco_pre_output_octets' => 'Cisco-Pre-Output-Octets',
- 'cisco_pre_output_packets' => 'Cisco-Pre-Output-Packets',
- 'cisco_presession_time' => 'Cisco-PreSession-Time',
- 'cisco_pw_lifetime' => 'Cisco-PW-Lifetime',
- 'cisco_route_ip' => 'Cisco-Route-IP',
- 'cisco_service_info' => 'Cisco-Service-Info',
- 'cisco_target_util' => 'Cisco-Target-Util',
- 'cisco_xmit_rate' => 'Cisco-Xmit-Rate',
- 'class' => 'Class',
- 'client_dns_pri' => 'Client_DNS_Pri',
- 'client_dns_prj' => 'Client-DNS-Pri',
- 'client_dns_sec' => 'Client_DNS_Sec',
- 'client_dns_sed' => 'Client-DNS-Sec',
- 'client_id' => 'Client-Id',
- 'client_ip_address' => 'Client-IP-Address',
- 'client_port_dnis' => 'Client-Port-DNIS',
- 'client_port_id' => 'Client-Port-Id',
- 'colubris_avpair' => 'Colubris-AVPair',
- 'configuration_token' => 'Configuration-Token',
- 'connect_info' => 'Connect-Info',
- 'connect_rate' => 'Connect-Rate',
- 'context_name' => 'Context_Name',
- 'context_namf' => 'Context-Name',
- 'crypt_password' => 'Crypt-Password',
- 'current_time' => 'Current-Time',
- 'cvpn3000_access_hours' => 'CVPN3000-Access-Hours',
- 'cvpn3000_allow_network_e' => 'CVPN3000-Allow-Network-Extension-Mode',
- 'cvpn3000_auth_server_pas' => 'CVPN3000-Auth-Server-Password',
- 'cvpn3000_auth_server_pri' => 'CVPN3000-Auth-Server-Priority',
- 'cvpn3000_auth_server_typ' => 'CVPN3000-Auth-Server-Type',
- 'cvpn3000_authd_user_idle' => 'CVPN3000-Authd-User-Idle-Timeout',
- 'cvpn3000_cisco_ip_phone_' => 'CVPN3000-Cisco-IP-Phone-Bypass',
- 'cvpn3000_dhcp_network_sc' => 'CVPN3000-DHCP-Network-Scope',
- 'cvpn3000_ike_keep_alives' => 'CVPN3000-IKE-Keep-Alives',
- 'cvpn3000_ipsec_allow_pas' => 'CVPN3000-IPSec-Allow-Passwd-Store',
- 'cvpn3000_ipsec_auth_on_r' => 'CVPN3000-IPSec-Auth-On-Rekey',
- 'cvpn3000_ipsec_authentic' => 'CVPN3000-IPSec-Authentication',
- 'cvpn3000_ipsec_authoriza' => 'CVPN3000-IPSec-Authorization-Type',
- 'cvpn3000_ipsec_authorizb' => 'CVPN3000-IPSec-Authorization-Required',
- 'cvpn3000_ipsec_backup_se' => 'CVPN3000-IPSec-Backup-Servers',
- 'cvpn3000_ipsec_backup_sf' => 'CVPN3000-IPSec-Backup-Server-List',
- 'cvpn3000_ipsec_banner1' => 'CVPN3000-IPSec-Banner1',
- 'cvpn3000_ipsec_banner2' => 'CVPN3000-IPSec-Banner2',
- 'cvpn3000_ipsec_client_fw' => 'CVPN3000-IPSec-Client-Fw-Filter-Name',
- 'cvpn3000_ipsec_client_fx' => 'CVPN3000-IPSec-Client-Fw-Filter-Opt',
- 'cvpn3000_ipsec_confidenc' => 'CVPN3000-IPSec-Confidence-Level',
- 'cvpn3000_ipsec_default_d' => 'CVPN3000-IPSec-Default-Domain',
- 'cvpn3000_ipsec_dn_field' => 'CVPN3000-IPSec-DN-Field',
- 'cvpn3000_ipsec_group_nam' => 'CVPN3000-IPSec-Group-Name',
- 'cvpn3000_ipsec_ike_peer_' => 'CVPN3000-IPSec-IKE-Peer-ID-Check',
- 'cvpn3000_ipsec_ip_compre' => 'CVPN3000-IPSec-IP-Compression',
- 'cvpn3000_ipsec_ltl_keepa' => 'CVPN3000-IPSec-LTL-Keepalives',
- 'cvpn3000_ipsec_mode_conf' => 'CVPN3000-IPSec-Mode-Config',
- 'cvpn3000_ipsec_over_udp' => 'CVPN3000-IPSec-Over-UDP',
- 'cvpn3000_ipsec_over_udp_' => 'CVPN3000-IPSec-Over-UDP-Port',
- 'cvpn3000_ipsec_reqrd_cli' => 'CVPN3000-IPSec-Reqrd-Client-Fw-Cap',
- 'cvpn3000_ipsec_sec_assoc' => 'CVPN3000-IPSec-Sec-Association',
- 'cvpn3000_ipsec_split_dns' => 'CVPN3000-IPSec-Split-DNS-Names',
- 'cvpn3000_ipsec_split_tun' => 'CVPN3000-IPSec-Split-Tunnel-List',
- 'cvpn3000_ipsec_split_tuo' => 'CVPN3000-IPSec-Split-Tunneling-Policy',
- 'cvpn3000_ipsec_tunnel_ty' => 'CVPN3000-IPSec-Tunnel-Type',
- 'cvpn3000_ipsec_user_grou' => 'CVPN3000-IPSec-User-Group-Lock',
- 'cvpn3000_l2tp_encryption' => 'CVPN3000-L2TP-Encryption',
- 'cvpn3000_l2tp_min_auth_p' => 'CVPN3000-L2TP-Min-Auth-Protocol',
- 'cvpn3000_l2tp_mppc_compr' => 'CVPN3000-L2TP-MPPC-Compression',
- 'cvpn3000_leap_bypass' => 'CVPN3000-LEAP-Bypass',
- 'cvpn3000_ms_client_icpt_' => 'CVPN3000-MS-Client-Icpt-DHCP-Conf-Msg',
- 'cvpn3000_ms_client_subne' => 'CVPN3000-MS-Client-Subnet-Mask',
- 'cvpn3000_partition_max_s' => 'CVPN3000-Partition-Max-Sessions',
- 'cvpn3000_partition_mobil' => 'CVPN3000-Partition-Mobile-IP-Key',
- 'cvpn3000_partition_mobim' => 'CVPN3000-Partition-Mobile-IP-Address',
- 'cvpn3000_partition_mobin' => 'CVPN3000-Partition-Mobile-IP-SPI',
- 'cvpn3000_partition_premi' => 'CVPN3000-Partition-Premise-Router',
- 'cvpn3000_partition_prima' => 'CVPN3000-Partition-Primary-DHCP',
- 'cvpn3000_partition_secon' => 'CVPN3000-Partition-Secondary-DHCP',
- 'cvpn3000_pptp_encryption' => 'CVPN3000-PPTP-Encryption',
- 'cvpn3000_pptp_min_auth_p' => 'CVPN3000-PPTP-Min-Auth-Protocol',
- 'cvpn3000_pptp_mppc_compr' => 'CVPN3000-PPTP-MPPC-Compression',
- 'cvpn3000_primary_dns' => 'CVPN3000-Primary-DNS',
- 'cvpn3000_primary_wins' => 'CVPN3000-Primary-WINS',
- 'cvpn3000_priority_on_sep' => 'CVPN3000-Priority-On-SEP',
- 'cvpn3000_reqrd_client_fw' => 'CVPN3000-Reqrd-Client-Fw-Vendor-Code',
- 'cvpn3000_reqrd_client_fx' => 'CVPN3000-Reqrd-Client-Fw-Product-Code',
- 'cvpn3000_reqrd_client_fy' => 'CVPN3000-Reqrd-Client-Fw-Description',
- 'cvpn3000_request_auth_ve' => 'CVPN3000-Request-Auth-Vector',
- 'cvpn3000_require_hw_clie' => 'CVPN3000-Require-HW-Client-Auth',
- 'cvpn3000_require_individ' => 'CVPN3000-Require-Individual-User-Auth',
- 'cvpn3000_secondary_dns' => 'CVPN3000-Secondary-DNS',
- 'cvpn3000_secondary_wins' => 'CVPN3000-Secondary-WINS',
- 'cvpn3000_sep_card_assign' => 'CVPN3000-SEP-Card-Assignment',
- 'cvpn3000_simultaneous_lo' => 'CVPN3000-Simultaneous-Logins',
- 'cvpn3000_strip_realm' => 'CVPN3000-Strip-Realm',
- 'cvpn3000_tunneling_proto' => 'CVPN3000-Tunneling-Protocols',
- 'cvpn3000_use_client_addr' => 'CVPN3000-Use-Client-Address',
- 'cvpn3000_user_auth_serve' => 'CVPN3000-User-Auth-Server-Name',
- 'cvpn3000_user_auth_servf' => 'CVPN3000-User-Auth-Server-Port',
- 'cvpn3000_user_auth_servg' => 'CVPN3000-User-Auth-Server-Secret',
- 'cvpn5000_client_assigned' => 'CVPN5000-Client-Assigned-IP',
- 'cvpn5000_client_assignee' => 'CVPN5000-Client-Assigned-IPX',
- 'cvpn5000_client_real_ip' => 'CVPN5000-Client-Real-IP',
- 'cvpn5000_echo' => 'CVPN5000-Echo',
- 'cvpn5000_tunnel_throughp' => 'CVPN5000-Tunnel-Throughput',
- 'cvpn5000_vpn_groupinfo' => 'CVPN5000-VPN-GroupInfo',
- 'cvpn5000_vpn_password' => 'CVPN5000-VPN-Password',
- 'cvx_assign_ip_pool' => 'CVX-Assign-IP-Pool',
- 'cvx_client_assign_dns' => 'CVX-Client-Assign-DNS',
- 'cvx_data_filter' => 'CVX-Data-Filter',
- 'cvx_data_rate' => 'CVX-Data-Rate',
- 'cvx_disconnect_cause' => 'CVX-Disconnect-Cause',
- 'cvx_identification' => 'CVX-Identification',
- 'cvx_idle_limit' => 'CVX-Idle-Limit',
- 'cvx_ipsvc_aznlvl' => 'CVX-IPSVC-AZNLVL',
- 'cvx_ipsvc_mask' => 'CVX-IPSVC-Mask',
- 'cvx_maximum_channels' => 'CVX-Maximum-Channels',
- 'cvx_modem_begin_modulati' => 'CVX-Modem-Begin-Modulation',
- 'cvx_modem_begin_recv_lin' => 'CVX-Modem-Begin-Recv-Line-Lvl',
- 'cvx_modem_data_compressi' => 'CVX-Modem-Data-Compression',
- 'cvx_modem_end_modulation' => 'CVX-Modem-End-Modulation',
- 'cvx_modem_end_recv_line_' => 'CVX-Modem-End-Recv-Line-Lvl',
- 'cvx_modem_error_correcti' => 'CVX-Modem-Error-Correction',
- 'cvx_modem_local_rate_neg' => 'CVX-Modem-Local-Rate-Negs',
- 'cvx_modem_local_retrains' => 'CVX-Modem-Local-Retrains',
- 'cvx_modem_remote_rate_ne' => 'CVX-Modem-Remote-Rate-Negs',
- 'cvx_modem_remote_retrain' => 'CVX-Modem-Remote-Retrains',
- 'cvx_modem_retx_packets' => 'CVX-Modem-ReTx-Packets',
- 'cvx_modem_snr' => 'CVX-Modem-SNR',
- 'cvx_modem_tx_packets' => 'CVX-Modem-Tx-Packets',
- 'cvx_multicast_client' => 'CVX-Multicast-Client',
- 'cvx_multicast_rate_limit' => 'CVX-Multicast-Rate-Limit',
- 'cvx_multilink_group_numb' => 'CVX-Multilink-Group-Number',
- 'cvx_multilink_match_info' => 'CVX-Multilink-Match-Info',
- 'cvx_ppp_address' => 'CVX-PPP-Address',
- 'cvx_ppp_log_mask' => 'CVX-PPP-Log-Mask',
- 'cvx_presession_time' => 'CVX-PreSession-Time',
- 'cvx_primary_dns' => 'CVX-Primary-DNS',
- 'cvx_radius_redirect' => 'CVX-Radius-Redirect',
- 'cvx_secondary_dns' => 'CVX-Secondary-DNS',
- 'cvx_ss7_session_id_type' => 'CVX-SS7-Session-ID-Type',
- 'cvx_vpop_id' => 'CVX-VPOP-ID',
- 'cvx_xmit_rate' => 'CVX-Xmit-Rate',
- 'dhcp_max_leases' => 'DHCP_Max_Leases',
- 'dhcp_max_leaset' => 'DHCP-Max-Leases',
- 'dialback_name' => 'Dialback-Name',
- 'dialback_no' => 'Dialback-No',
- 'digest_algorithm' => 'Digest-Algorithm',
- 'digest_attributes' => 'Digest-Attributes',
- 'digest_body_digest' => 'Digest-Body-Digest',
- 'digest_cnonce' => 'Digest-CNonce',
- 'digest_method' => 'Digest-Method',
- 'digest_nonce' => 'Digest-Nonce',
- 'digest_nonce_count' => 'Digest-Nonce-Count',
- 'digest_qop' => 'Digest-QOP',
- 'digest_realm' => 'Digest-Realm',
- 'digest_response' => 'Digest-Response',
- 'digest_uri' => 'Digest-URI',
- 'digest_user_name' => 'Digest-User-Name',
- 'eap_code' => 'EAP-Code',
- 'eap_id' => 'EAP-Id',
- 'eap_md5_password' => 'EAP-MD5-Password',
- 'eap_message' => 'EAP-Message',
- 'eap_sim_any_id_req' => 'EAP-Sim-ANY_ID_REQ',
- 'eap_sim_checkcode' => 'EAP-Sim-CHECKCODE',
- 'eap_sim_counter' => 'EAP-Sim-COUNTER',
- 'eap_sim_counter_too_smal' => 'EAP-Sim-COUNTER_TOO_SMALL',
- 'eap_sim_encr_data' => 'EAP-Sim-ENCR_DATA',
- 'eap_sim_extra' => 'EAP-Sim-EXTRA',
- 'eap_sim_fullauth_id_req' => 'EAP-Sim-FULLAUTH_ID_REQ',
- 'eap_sim_hmac' => 'EAP-Sim-HMAC',
- 'eap_sim_identity' => 'EAP-Sim-IDENTITY',
- 'eap_sim_imsi' => 'EAP-Sim-IMSI',
- 'eap_sim_iv' => 'EAP-Sim-IV',
- 'eap_sim_kc1' => 'EAP-Sim-KC1',
- 'eap_sim_kc2' => 'EAP-Sim-KC2',
- 'eap_sim_kc3' => 'EAP-Sim-KC3',
- 'eap_sim_key' => 'EAP-Sim-KEY',
- 'eap_sim_mac' => 'EAP-Sim-MAC',
- 'eap_sim_next_pseudonum' => 'EAP-Sim-NEXT_PSEUDONUM',
- 'eap_sim_next_reauth_id' => 'EAP-Sim-NEXT_REAUTH_ID',
- 'eap_sim_nonce_mt' => 'EAP-Sim-NONCE_MT',
- 'eap_sim_nonce_s' => 'EAP-Sim-NONCE_S',
- 'eap_sim_notification' => 'EAP-Sim-NOTIFICATION',
- 'eap_sim_padding' => 'EAP-Sim-PADDING',
- 'eap_sim_permanent_id_req' => 'EAP-Sim-PERMANENT_ID_REQ',
- 'eap_sim_rand' => 'EAP-Sim-RAND',
- 'eap_sim_rand1' => 'EAP-Sim-Rand1',
- 'eap_sim_rand2' => 'EAP-Sim-Rand2',
- 'eap_sim_rand3' => 'EAP-Sim-Rand3',
- 'eap_sim_selected_version' => 'EAP-Sim-SELECTED_VERSION',
- 'eap_sim_sres1' => 'EAP-Sim-SRES1',
- 'eap_sim_sres2' => 'EAP-Sim-SRES2',
- 'eap_sim_sres3' => 'EAP-Sim-SRES3',
- 'eap_sim_state' => 'EAP-Sim-State',
- 'eap_sim_subtype' => 'EAP-Sim-Subtype',
- 'eap_sim_version_list' => 'EAP-Sim-VERSION_LIST',
- 'eap_tls_require_client_c' => 'EAP-TLS-Require-Client-Cert',
- 'eap_type' => 'EAP-Type',
- 'eap_type_gtc' => 'EAP-Type-GTC',
- 'eap_type_identity' => 'EAP-Type-Identity',
- 'eap_type_leap' => 'EAP-Type-LEAP',
- 'eap_type_md5' => 'EAP-Type-MD5',
- 'eap_type_nak' => 'EAP-Type-NAK',
- 'eap_type_notification' => 'EAP-Type-Notification',
- 'eap_type_otp' => 'EAP-Type-OTP',
- 'eap_type_peap' => 'EAP-Type-PEAP',
- 'eap_type_sim' => 'EAP-Type-SIM',
- 'eap_type_sim2' => 'EAP-Type-SIM2',
- 'eap_type_tls' => 'EAP-Type-TLS',
- 'eap_type_ttls' => 'EAP-Type-TTLS',
- 'error_cause' => 'Error-Cause',
- 'erx_address_pool_name' => 'ERX-Address-Pool-Name',
- 'erx_alternate_cli_access' => 'ERX-Alternate-Cli-Access-Level',
- 'erx_alternate_cli_vroute' => 'ERX-Alternate-Cli-Vrouter-Name',
- 'erx_atm_mbs' => 'ERX-Atm-MBS',
- 'erx_atm_pcr' => 'ERX-Atm-PCR',
- 'erx_atm_scr' => 'ERX-Atm-SCR',
- 'erx_atm_service_category' => 'ERX-Atm-Service-Category',
- 'erx_bearer_type' => 'ERX-Bearer-Type',
- 'erx_cli_allow_all_vr_acc' => 'ERX-Cli-Allow-All-VR-Access',
- 'erx_cli_initial_access_l' => 'ERX-Cli-Initial-Access-Level',
- 'erx_dial_out_number' => 'ERX-Dial-Out-Number',
- 'erx_egress_policy_name' => 'ERX-Egress-Policy-Name',
- 'erx_egress_statistics' => 'ERX-Egress-Statistics',
- 'erx_framed_ip_route_tag' => 'ERX-Framed-Ip-Route-Tag',
- 'erx_igmp_enable' => 'ERX-Igmp-Enable',
- 'erx_ingress_policy_name' => 'ERX-Ingress-Policy-Name',
- 'erx_ingress_statistics' => 'ERX-Ingress-Statistics',
- 'erx_input_gigapkts' => 'ERX-Input-Gigapkts',
- 'erx_ipv6_local_interface' => 'ERX-IpV6-Local-Interface',
- 'erx_ipv6_primary_dns' => 'ERX-Ipv6-Primary-Dns',
- 'erx_ipv6_secondary_dns' => 'ERX-Ipv6-Secondary-Dns',
- 'erx_ipv6_virtual_router' => 'ERX-IpV6-Virtual-Router',
- 'erx_local_loopback_inter' => 'ERX-Local-Loopback-Interface',
- 'erx_maximum_bps' => 'ERX-Maximum-BPS',
- 'erx_minimum_bps' => 'ERX-Minimum-BPS',
- 'erx_output_gigapkts' => 'ERX-Output-Gigapkts',
- 'erx_ppp_auth_protocol' => 'ERX-PPP-Auth-Protocol',
- 'erx_ppp_password' => 'ERX-PPP-Password',
- 'erx_ppp_username' => 'ERX-PPP-Username',
- 'erx_pppoe_description' => 'ERX-Pppoe-Description',
- 'erx_pppoe_max_sessions' => 'ERX-Pppoe-Max-Sessions',
- 'erx_pppoe_url' => 'ERX-Pppoe-Url',
- 'erx_primary_dns' => 'ERX-Primary-Dns',
- 'erx_primary_wins' => 'ERX-Primary-Wins',
- 'erx_qos_profile_interfac' => 'ERX-Qos-Profile-Interface-Type',
- 'erx_qos_profile_name' => 'ERX-Qos-Profile-Name',
- 'erx_redirect_vr_name' => 'ERX-Redirect-VR-Name',
- 'erx_sa_validate' => 'ERX-Sa-Validate',
- 'erx_secondary_dns' => 'ERX-Secondary-Dns',
- 'erx_secondary_wins' => 'ERX-Secondary-Wins',
- 'erx_service_bundle' => 'ERX-Service-Bundle',
- 'erx_tunnel_interface_id' => 'ERX-Tunnel-Interface-Id',
- 'erx_tunnel_maximum_sessi' => 'ERX-Tunnel-Maximum-Sessions',
- 'erx_tunnel_nas_port_meth' => 'ERX-Tunnel-Nas-Port-Method',
- 'erx_tunnel_password' => 'ERX-Tunnel-Password',
- 'erx_tunnel_tos' => 'ERX-Tunnel-Tos',
- 'erx_tunnel_virtual_route' => 'ERX-Tunnel-Virtual-Router',
- 'erx_virtual_router_name' => 'ERX-Virtual-Router-Name',
- 'event_timestamp' => 'Event-Timestamp',
- 'exec_program' => 'Exec-Program',
- 'exec_program_wait' => 'Exec-Program-Wait',
- 'expiration' => 'Expiration',
- 'extreme_netlogin_only' => 'Extreme-Netlogin-Only',
- 'extreme_netlogin_url' => 'Extreme-Netlogin-Url',
- 'extreme_netlogin_url_des' => 'Extreme-Netlogin-Url-Desc',
- 'extreme_netlogin_vlan' => 'Extreme-Netlogin-Vlan',
- 'fall_through' => 'Fall-Through',
- 'filter_id' => 'Filter-Id',
- 'foundry_command_exceptio' => 'Foundry-Command-Exception-Flag',
- 'foundry_command_string' => 'Foundry-Command-String',
- 'foundry_inm_privilege' => 'Foundry-INM-Privilege',
- 'foundry_privilege_level' => 'Foundry-Privilege-Level',
- 'framed_address' => 'Framed-Address',
- 'framed_appletalk_link' => 'Framed-AppleTalk-Link',
- 'framed_appletalk_network' => 'Framed-AppleTalk-Network',
- 'framed_appletalk_zone' => 'Framed-AppleTalk-Zone',
- 'framed_callback_id' => 'Framed-Callback-Id',
- 'framed_compression' => 'Framed-Compression',
- 'framed_filter_id' => 'Framed-Filter-Id',
- 'framed_interface_id' => 'Framed-Interface-Id',
- 'framed_ip_address' => 'Framed-IP-Address',
- 'framed_ip_netmask' => 'Framed-IP-Netmask',
- 'framed_ipv6_pool' => 'Framed-IPv6-Pool',
- 'framed_ipv6_prefix' => 'Framed-IPv6-Prefix',
- 'framed_ipv6_route' => 'Framed-IPv6-Route',
- 'framed_ipx_network' => 'Framed-IPX-Network',
- 'framed_mtu' => 'Framed-MTU',
- 'framed_netmask' => 'Framed-Netmask',
- 'framed_pool' => 'Framed-Pool',
- 'framed_protocol' => 'Framed-Protocol',
- 'framed_route' => 'Framed-Route',
- 'framed_routing' => 'Framed-Routing',
- 'freeradius_proxied_to' => 'FreeRADIUS-Proxied-To',
- 'gandalf_around_the_corne' => 'Gandalf-Around-The-Corner',
- 'gandalf_authentication_s' => 'Gandalf-Authentication-String',
- 'gandalf_calling_line_id_' => 'Gandalf-Calling-Line-ID-1',
- 'gandalf_calling_line_ida' => 'Gandalf-Calling-Line-ID-2',
- 'gandalf_channel_group_na' => 'Gandalf-Channel-Group-Name-1',
- 'gandalf_channel_group_nb' => 'Gandalf-Channel-Group-Name-2',
- 'gandalf_compression_stat' => 'Gandalf-Compression-Status',
- 'gandalf_dial_prefix_name' => 'Gandalf-Dial-Prefix-Name-1',
- 'gandalf_dial_prefix_namf' => 'Gandalf-Dial-Prefix-Name-2',
- 'gandalf_fwd_broadcast_in' => 'Gandalf-Fwd-Broadcast-In',
- 'gandalf_fwd_broadcast_ou' => 'Gandalf-Fwd-Broadcast-Out',
- 'gandalf_fwd_multicast_in' => 'Gandalf-Fwd-Multicast-In',
- 'gandalf_fwd_multicast_ou' => 'Gandalf-Fwd-Multicast-Out',
- 'gandalf_fwd_unicast_in' => 'Gandalf-Fwd-Unicast-In',
- 'gandalf_fwd_unicast_out' => 'Gandalf-Fwd-Unicast-Out',
- 'gandalf_hunt_group' => 'Gandalf-Hunt-Group',
- 'gandalf_ipx_spoofing_sta' => 'Gandalf-IPX-Spoofing-State',
- 'gandalf_ipx_watchdog_spo' => 'Gandalf-IPX-Watchdog-Spoof',
- 'gandalf_min_outgoing_bea' => 'Gandalf-Min-Outgoing-Bearer',
- 'gandalf_modem_mode' => 'Gandalf-Modem-Mode',
- 'gandalf_modem_required_1' => 'Gandalf-Modem-Required-1',
- 'gandalf_modem_required_2' => 'Gandalf-Modem-Required-2',
- 'gandalf_operational_mode' => 'Gandalf-Operational-Modes',
- 'gandalf_phone_number_1' => 'Gandalf-Phone-Number-1',
- 'gandalf_phone_number_2' => 'Gandalf-Phone-Number-2',
- 'gandalf_ppp_authenticati' => 'Gandalf-PPP-Authentication',
- 'gandalf_ppp_ncp_type' => 'Gandalf-PPP-NCP-Type',
- 'gandalf_remote_lan_name' => 'Gandalf-Remote-LAN-Name',
- 'gandalf_sap_group_name_1' => 'Gandalf-SAP-Group-Name-1',
- 'gandalf_sap_group_name_2' => 'Gandalf-SAP-Group-Name-2',
- 'gandalf_sap_group_name_3' => 'Gandalf-SAP-Group-Name-3',
- 'gandalf_sap_group_name_4' => 'Gandalf-SAP-Group-Name-4',
- 'gandalf_sap_group_name_5' => 'Gandalf-SAP-Group-Name-5',
- 'garderos_location_name' => 'Garderos-Location-Name',
- 'garderos_service_name' => 'Garderos-Service-Name',
- 'group' => 'Group',
- 'group_name' => 'Group-Name',
- 'gw_final_xlated_cdn' => 'gw-final-xlated-cdn',
- 'gw_rxd_cdn' => 'gw-rxd-cdn',
- 'h323_billing_model' => 'h323-billing-model',
- 'h323_call_origin' => 'h323-call-origin',
- 'h323_call_type' => 'h323-call-type',
- 'h323_conf_id' => 'h323-conf-id',
- 'h323_connect_time' => 'h323-connect-time',
- 'h323_credit_amount' => 'h323-credit-amount',
- 'h323_credit_time' => 'h323-credit-time',
- 'h323_currency' => 'h323-currency',
- 'h323_disconnect_cause' => 'h323-disconnect-cause',
- 'h323_disconnect_time' => 'h323-disconnect-time',
- 'h323_gw_id' => 'h323-gw-id',
- 'h323_incoming_conf_id' => 'h323-incoming-conf-id',
- 'h323_preferred_lang' => 'h323-preferred-lang',
- 'h323_prompt_id' => 'h323-prompt-id',
- 'h323_redirect_ip_address' => 'h323-redirect-ip-address',
- 'h323_redirect_number' => 'h323-redirect-number',
- 'h323_remote_address' => 'h323-remote-address',
- 'h323_return_code' => 'h323-return-code',
- 'h323_setup_time' => 'h323-setup-time',
- 'h323_time_and_day' => 'h323-time-and-day',
- 'h323_voice_quality' => 'h323-voice-quality',
- 'hint' => 'Hint',
- 'huntgroup_name' => 'Huntgroup-Name',
- 'idle_timeout' => 'Idle-Timeout',
- 'incoming_req_uri' => 'incoming-req-uri',
- 'initial_modulation_type' => 'Initial-Modulation-Type',
- 'ip3_ip_option' => 'IP3-IP-Option',
- 'ip3_rdata_rate' => 'IP3-RData-Rate',
- 'ip3_xdata_rate' => 'IP3-XData-Rate',
- 'ip_address_pool_name' => 'Ip_Address_Pool_Name',
- 'ip_address_pool_namf' => 'Ip-Address-Pool-Name',
- 'ip_host_addr' => 'Ip_Host_Addr',
- 'ip_host_adds' => 'Ip-Host-Addr',
- 'ip_tos_field' => 'IP_TOS_Field',
- 'ip_tos_fiele' => 'IP-TOS-Field',
- 'itk_acct_serv_ip' => 'ITK-Acct-Serv-IP',
- 'itk_acct_serv_prot' => 'ITK-Acct-Serv-Prot',
- 'itk_auth_req_type' => 'ITK-Auth-Req-Type',
- 'itk_auth_serv_ip' => 'ITK-Auth-Serv-IP',
- 'itk_auth_serv_prot' => 'ITK-Auth-Serv-Prot',
- 'itk_banner' => 'ITK-Banner',
- 'itk_channel_binding' => 'ITK-Channel-Binding',
- 'itk_ddi' => 'ITK-DDI',
- 'itk_dest_no' => 'ITK-Dest-No',
- 'itk_dialout_type' => 'ITK-Dialout-Type',
- 'itk_filter_rule' => 'ITK-Filter-Rule',
- 'itk_ftp_auth_ip' => 'ITK-Ftp-Auth-IP',
- 'itk_ip_pool' => 'ITK-IP-Pool',
- 'itk_isdn_prot' => 'ITK-ISDN-Prot',
- 'itk_modem_init_string' => 'ITK-Modem-Init-String',
- 'itk_modem_pool_id' => 'ITK-Modem-Pool-Id',
- 'itk_nas_name' => 'ITK-NAS-Name',
- 'itk_password_prompt' => 'ITK-Password-Prompt',
- 'itk_ppp_auth_type' => 'ITK-PPP-Auth-Type',
- 'itk_ppp_client_server_mo' => 'ITK-PPP-Client-Server-Mode',
- 'itk_ppp_compression_prot' => 'ITK-PPP-Compression-Prot',
- 'itk_prompt' => 'ITK-Prompt',
- 'itk_provider_id' => 'ITK-Provider-Id',
- 'itk_start_delay' => 'ITK-Start-Delay',
- 'itk_tunnel_ip' => 'ITK-Tunnel-IP',
- 'itk_tunnel_prot' => 'ITK-Tunnel-Prot',
- 'itk_usergroup' => 'ITK-Usergroup',
- 'itk_username' => 'ITK-Username',
- 'itk_username_prompt' => 'ITK-Username-Prompt',
- 'itk_users_default_entry' => 'ITK-Users-Default-Entry',
- 'itk_users_default_pw' => 'ITK-Users-Default-Pw',
- 'itk_welcome_message' => 'ITK-Welcome-Message',
- 'juniper_allow_commands' => 'Juniper-Allow-Commands',
- 'juniper_allow_configurat' => 'Juniper-Allow-Configuration',
- 'juniper_deny_commands' => 'Juniper-Deny-Commands',
- 'juniper_deny_configurati' => 'Juniper-Deny-Configuration',
- 'juniper_local_user_name' => 'Juniper-Local-User-Name',
- 'karlnet_turbocell_name' => 'KarlNet-TurboCell-Name',
- 'karlnet_turbocell_opmode' => 'KarlNet-TurboCell-OpMode',
- 'karlnet_turbocell_opstat' => 'KarlNet-TurboCell-OpState',
- 'karlnet_turbocell_txrate' => 'KarlNet-TurboCell-TxRate',
- 'lac_port' => 'LAC_Port',
- 'lac_port_type' => 'LAC_Port_Type',
- 'lac_port_typf' => 'LAC-Port-Type',
- 'lac_poru' => 'LAC-Port',
- 'lac_real_port' => 'LAC_Real_Port',
- 'lac_real_port_type' => 'LAC_Real_Port_Type',
- 'lac_real_port_typf' => 'LAC-Real-Port-Type',
- 'lac_real_poru' => 'LAC-Real-Port',
- 'ldap_group' => 'Ldap-Group',
- 'ldap_userdn' => 'Ldap-UserDn',
- 'le_admin_group' => 'LE-Admin-Group',
- 'le_advice_of_charge' => 'LE-Advice-of-Charge',
- 'le_connect_detail' => 'LE-Connect-Detail',
- 'le_ip_gateway' => 'LE-IP-Gateway',
- 'le_ip_pool' => 'LE-IP-Pool',
- 'le_ipsec_active_profile' => 'LE-IPSec-Active-Profile',
- 'le_ipsec_deny_action' => 'LE-IPSec-Deny-Action',
- 'le_ipsec_log_options' => 'LE-IPSec-Log-Options',
- 'le_ipsec_outsource_profi' => 'LE-IPSec-Outsource-Profile',
- 'le_ipsec_passive_profile' => 'LE-IPSec-Passive-Profile',
- 'le_modem_info' => 'LE-Modem-Info',
- 'le_multicast_client' => 'LE-Multicast-Client',
- 'le_nat_inmap' => 'LE-NAT-Inmap',
- 'le_nat_log_options' => 'LE-NAT-Log-Options',
- 'le_nat_other_session_tim' => 'LE-NAT-Other-Session-Timeout',
- 'le_nat_outmap' => 'LE-NAT-Outmap',
- 'le_nat_outsource_inmap' => 'LE-NAT-Outsource-Inmap',
- 'le_nat_outsource_outmap' => 'LE-NAT-Outsource-Outmap',
- 'le_nat_sess_dir_fail_act' => 'LE-NAT-Sess-Dir-Fail-Action',
- 'le_nat_tcp_session_timeo' => 'LE-NAT-TCP-Session-Timeout',
- 'le_terminate_detail' => 'LE-Terminate-Detail',
- 'lm_password' => 'LM-Password',
- 'local_web_acct_duration' => 'Local-Web-Acct-Duration',
- 'local_web_acct_interim_r' => 'Local-Web-Acct-Interim-Rx-Bytes',
- 'local_web_acct_interim_s' => 'Local-Web-Acct-Interim-Rx-Gigawords',
- 'local_web_acct_interim_t' => 'Local-Web-Acct-Interim-Tx-Bytes',
- 'local_web_acct_interim_u' => 'Local-Web-Acct-Interim-Tx-Gigawords',
- 'local_web_acct_interim_v' => 'Local-Web-Acct-Interim-Tx-Mgmt',
- 'local_web_acct_interim_w' => 'Local-Web-Acct-Interim-Rx-Mgmt',
- 'local_web_acct_rx_mgmt' => 'Local-Web-Acct-Rx-Mgmt',
- 'local_web_acct_time' => 'Local-Web-Acct-Time',
- 'local_web_acct_tx_mgmt' => 'Local-Web-Acct-Tx-Mgmt',
- 'local_web_border_router' => 'Local-Web-Border-Router',
- 'local_web_client_ip' => 'Local-Web-Client-Ip',
- 'local_web_reauth_counter' => 'Local-Web-Reauth-Counter',
- 'local_web_rx_limit' => 'Local-Web-Rx-Limit',
- 'local_web_tx_limit' => 'Local-Web-Tx-Limit',
- 'login_callback_number' => 'Login-Callback-Number',
- 'login_host' => 'Login-Host',
- 'login_ip_host' => 'Login-IP-Host',
- 'login_ipv6_host' => 'Login-IPv6-Host',
- 'login_lat_group' => 'Login-LAT-Group',
- 'login_lat_node' => 'Login-LAT-Node',
- 'login_lat_port' => 'Login-LAT-Port',
- 'login_lat_service' => 'Login-LAT-Service',
- 'login_port' => 'Login-Port',
- 'login_service' => 'Login-Service',
- 'login_tcp_port' => 'Login-TCP-Port',
- 'login_time' => 'Login-Time',
- 'mcast_maxgroups' => 'Mcast_MaxGroups',
- 'mcast_maxgroupt' => 'Mcast-MaxGroups',
- 'mcast_receive' => 'Mcast_Receive',
- 'mcast_receivf' => 'Mcast-Receive',
- 'mcast_send' => 'Mcast_Send',
- 'mcast_sene' => 'Mcast-Send',
- 'medium_type' => 'Medium_Type',
- 'medium_typf' => 'Medium-Type',
- 'menu' => 'Menu',
- 'merit_proxy_action' => 'Merit-Proxy-Action',
- 'merit_user_id' => 'Merit-User-Id',
- 'merit_user_realm' => 'Merit-User-Realm',
- 'message_authenticator' => 'Message-Authenticator',
- 'method' => 'method',
- 'mikrotik_group' => 'Mikrotik-Group',
- 'mikrotik_recv_limit' => 'Mikrotik-Recv-Limit',
- 'mikrotik_xmit_limit' => 'Mikrotik-Xmit-Limit',
- 'module_failure_message' => 'Module-Failure-Message',
- 'module_success_message' => 'Module-Success-Message',
- 'motorola_canopy_cirenabl' => 'Motorola-Canopy-CIRENABLE',
- 'motorola_canopy_dlba' => 'Motorola-Canopy-DLBA',
- 'motorola_canopy_enable' => 'Motorola-Canopy-Enable',
- 'motorola_canopy_higherbw' => 'Motorola-Canopy-HIGHERBW',
- 'motorola_canopy_hpcenabl' => 'Motorola-Canopy-HPCENABLE',
- 'motorola_canopy_hpsdldr' => 'Motorola-Canopy-HPSDLDR',
- 'motorola_canopy_hpsuldr' => 'Motorola-Canopy-HPSULDR',
- 'motorola_canopy_lpsdldr' => 'Motorola-Canopy-LPSDLDR',
- 'motorola_canopy_lpsuldr' => 'Motorola-Canopy-LPSULDR',
- 'motorola_canopy_sdldr' => 'Motorola-Canopy-SDLDR',
- 'motorola_canopy_shared_s' => 'Motorola-Canopy-Shared-Secret',
- 'motorola_canopy_suldr' => 'Motorola-Canopy-SULDR',
- 'motorola_canopy_ulba' => 'Motorola-Canopy-ULBA',
- 'ms_acct_auth_type' => 'MS-Acct-Auth-Type',
- 'ms_acct_eap_type' => 'MS-Acct-EAP-Type',
- 'ms_arap_pw_change_reason' => 'MS-ARAP-PW-Change-Reason',
- 'ms_bap_usage' => 'MS-BAP-Usage',
- 'ms_chap2_cpw' => 'MS-CHAP2-CPW',
- 'ms_chap2_response' => 'MS-CHAP2-Response',
- 'ms_chap2_success' => 'MS-CHAP2-Success',
- 'ms_chap_challenge' => 'MS-CHAP-Challenge',
- 'ms_chap_cpw_1' => 'MS-CHAP-CPW-1',
- 'ms_chap_cpw_2' => 'MS-CHAP-CPW-2',
- 'ms_chap_domain' => 'MS-CHAP-Domain',
- 'ms_chap_error' => 'MS-CHAP-Error',
- 'ms_chap_lm_enc_pw' => 'MS-CHAP-LM-Enc-PW',
- 'ms_chap_mppe_keys' => 'MS-CHAP-MPPE-Keys',
- 'ms_chap_nt_enc_pw' => 'MS-CHAP-NT-Enc-PW',
- 'ms_chap_response' => 'MS-CHAP-Response',
- 'ms_chap_use_ntlm_auth' => 'MS-CHAP-Use-NTLM-Auth',
- 'ms_filter' => 'MS-Filter',
- 'ms_link_drop_time_limit' => 'MS-Link-Drop-Time-Limit',
- 'ms_link_utilization_thre' => 'MS-Link-Utilization-Threshold',
- 'ms_mppe_encryption_polic' => 'MS-MPPE-Encryption-Policy',
- 'ms_mppe_encryption_type' => 'MS-MPPE-Encryption-Type',
- 'ms_mppe_encryption_types' => 'MS-MPPE-Encryption-Types',
- 'ms_mppe_recv_key' => 'MS-MPPE-Recv-Key',
- 'ms_mppe_send_key' => 'MS-MPPE-Send-Key',
- 'ms_new_arap_password' => 'MS-New-ARAP-Password',
- 'ms_old_arap_password' => 'MS-Old-ARAP-Password',
- 'ms_primary_dns_server' => 'MS-Primary-DNS-Server',
- 'ms_primary_nbns_server' => 'MS-Primary-NBNS-Server',
- 'ms_ras_vendor' => 'MS-RAS-Vendor',
- 'ms_ras_version' => 'MS-RAS-Version',
- 'ms_secondary_dns_server' => 'MS-Secondary-DNS-Server',
- 'ms_secondary_nbns_server' => 'MS-Secondary-NBNS-Server',
- 'multi_link_flag' => 'Multi-Link-Flag',
- 'nas_identifier' => 'NAS-Identifier',
- 'nas_ip_address' => 'NAS-IP-Address',
- 'nas_ipv6_address' => 'NAS-IPv6-Address',
- 'nas_port' => 'NAS-Port',
- 'nas_port_id' => 'NAS-Port-Id',
- 'nas_port_type' => 'NAS-Port-Type',
- 'nas_real_port' => 'NAS_Real_Port',
- 'nas_real_poru' => 'NAS-Real-Port',
- 'navini_avpair' => 'Navini-AVPair',
- 'next_hop_dn' => 'next-hop-dn',
- 'next_hop_ip' => 'next-hop-ip',
- 'nn_data_rate' => 'NN-Data-Rate',
- 'nn_data_rate_ceiling' => 'NN-Data-Rate-Ceiling',
- 'nn_homenode' => 'NN-Homenode',
- 'nn_homeservice' => 'NN-Homeservice',
- 'nn_homeservice_name' => 'NN-Homeservice-Name',
- 'no_such_attribute' => 'No-Such-Attribute',
- 'nokia_charging_id' => 'Nokia-Charging-Id',
- 'nokia_ggsn_ip_address' => 'Nokia-GGSN-IP-Address',
- 'nokia_imsi' => 'Nokia-IMSI',
- 'nokia_prepaid_ind' => 'Nokia-Prepaid-Ind',
- 'nokia_sgsn_ip_address' => 'Nokia-SGSN-IP-Address',
- 'nomadix_bw_down' => 'Nomadix-Bw-Down',
- 'nomadix_bw_up' => 'Nomadix-Bw-Up',
- 'nomadix_config_url' => 'Nomadix-Config-URL',
- 'nomadix_endofsession' => 'Nomadix-EndofSession',
- 'nomadix_expiration' => 'Nomadix-Expiration',
- 'nomadix_goodbye_url' => 'Nomadix-Goodbye-URL',
- 'nomadix_ip_upsell' => 'Nomadix-IP-Upsell',
- 'nomadix_logoff_url' => 'Nomadix-Logoff-URL',
- 'nomadix_maxbytesdown' => 'Nomadix-MaxBytesDown',
- 'nomadix_maxbytesup' => 'Nomadix-MaxBytesUp',
- 'nomadix_net_vlan' => 'Nomadix-Net-VLAN',
- 'nomadix_subnet' => 'Nomadix-Subnet',
- 'nomadix_url_redirection' => 'Nomadix-URL-Redirection',
- 'ns_admin_privilege' => 'NS-Admin-Privilege',
- 'ns_mta_md5_password' => 'NS-MTA-MD5-Password',
- 'ns_primary_dns' => 'NS-Primary-DNS',
- 'ns_primary_wins' => 'NS-Primary-WINS',
- 'ns_secondary_dns' => 'NS-Secondary-DNS',
- 'ns_secondary_wins' => 'NS-Secondary-WINS',
- 'ns_user_group' => 'NS-User-Group',
- 'ns_vsys_name' => 'NS-VSYS-Name',
- 'nt_password' => 'NT-Password',
- 'ntlm_user_name' => 'NTLM-User-Name',
- 'old_password' => 'Old-Password',
- 'outgoing_req_uri' => 'outgoing-req-uri',
- 'packet_dst_port' => 'Packet-Dst-Port',
- 'packet_type' => 'Packet-Type',
- 'pam_auth' => 'Pam-Auth',
- 'password' => 'Password',
- 'password_retry' => 'Password-Retry',
- 'police_burst' => 'Police_Burst',
- 'police_bursu' => 'Police-Burst',
- 'police_rate' => 'Police_Rate',
- 'police_ratf' => 'Police-Rate',
- 'pool_name' => 'Pool-Name',
- 'port_limit' => 'Port-Limit',
- 'port_message' => 'Port-Message',
- 'post_auth_type' => 'Post-Auth-Type',
- 'post_proxy_type' => 'Post-Proxy-Type',
- 'postauth_type' => 'PostAuth-Type',
- 'pppoe_motm' => 'PPPOE_MOTM',
- 'pppoe_motn' => 'PPPOE-MOTM',
- 'pppoe_url' => 'PPPOE_URL',
- 'pppoe_urm' => 'PPPOE-URL',
- 'pre_acct_type' => 'Pre-Acct-Type',
- 'pre_proxy_type' => 'Pre-Proxy-Type',
- 'prefix' => 'Prefix',
- 'prev_hop_ip' => 'prev-hop-ip',
- 'prev_hop_via' => 'prev-hop-via',
- 'prompt' => 'Prompt',
- 'propel_accelerate' => 'Propel-Accelerate',
- 'propel_client_ip_address' => 'Propel-Client-IP-Address',
- 'propel_client_nas_ip_add' => 'Propel-Client-NAS-IP-Address',
- 'propel_client_source_id' => 'Propel-Client-Source-ID',
- 'propel_dialed_digits' => 'Propel-Dialed-Digits',
- 'proxy_state' => 'Proxy-State',
- 'proxy_to_realm' => 'Proxy-To-Realm',
- 'pvc_circuit_padding' => 'PVC_Circuit_Padding',
- 'pvc_circuit_paddinh' => 'PVC-Circuit-Padding',
- 'pvc_encapsulation_type' => 'PVC_Encapsulation_Type',
- 'pvc_encapsulation_typf' => 'PVC-Encapsulation-Type',
- 'pvc_profile_name' => 'PVC_Profile_Name',
- 'pvc_profile_namf' => 'PVC-Profile-Name',
- 'quintum_avpair' => 'Quintum-AVPair',
- 'quintum_h323_billing_mod' => 'Quintum-h323-billing-model',
- 'quintum_h323_call_origin' => 'Quintum-h323-call-origin',
- 'quintum_h323_call_type' => 'Quintum-h323-call-type',
- 'quintum_h323_conf_id' => 'Quintum-h323-conf-id',
- 'quintum_h323_connect_tim' => 'Quintum-h323-connect-time',
- 'quintum_h323_credit_amou' => 'Quintum-h323-credit-amount',
- 'quintum_h323_credit_time' => 'Quintum-h323-credit-time',
- 'quintum_h323_currency_ty' => 'Quintum-h323-currency-type',
- 'quintum_h323_disconnect_' => 'Quintum-h323-disconnect-time',
- 'quintum_h323_disconnecta' => 'Quintum-h323-disconnect-cause',
- 'quintum_h323_gw_id' => 'Quintum-h323-gw-id',
- 'quintum_h323_incoming_co' => 'Quintum-h323-incoming-conf-id',
- 'quintum_h323_preferred_l' => 'Quintum-h323-preferred-lang',
- 'quintum_h323_prompt_id' => 'Quintum-h323-prompt-id',
- 'quintum_h323_redirect_ip' => 'Quintum-h323-redirect-ip-address',
- 'quintum_h323_redirect_nu' => 'Quintum-h323-redirect-number',
- 'quintum_h323_remote_addr' => 'Quintum-h323-remote-address',
- 'quintum_h323_return_code' => 'Quintum-h323-return-code',
- 'quintum_h323_setup_time' => 'Quintum-h323-setup-time',
- 'quintum_h323_time_and_da' => 'Quintum-h323-time-and-day',
- 'quintum_h323_voice_quali' => 'Quintum-h323-voice-quality',
- 'quintum_nas_port' => 'Quintum-NAS-Port',
- 'rate_limit_burst' => 'Rate_Limit_Burst',
- 'rate_limit_bursu' => 'Rate-Limit-Burst',
- 'rate_limit_rate' => 'Rate_Limit_Rate',
- 'rate_limit_ratf' => 'Rate-Limit-Rate',
- 'realm' => 'Realm',
- 'redcreek_tunneled_dns_se' => 'RedCreek-Tunneled-DNS-Server',
- 'redcreek_tunneled_domain' => 'RedCreek-Tunneled-DomainName',
- 'redcreek_tunneled_gatewa' => 'RedCreek-Tunneled-Gateway',
- 'redcreek_tunneled_hostna' => 'RedCreek-Tunneled-HostName',
- 'redcreek_tunneled_ip_add' => 'RedCreek-Tunneled-IP-Addr',
- 'redcreek_tunneled_ip_net' => 'RedCreek-Tunneled-IP-Netmask',
- 'redcreek_tunneled_search' => 'RedCreek-Tunneled-Search-List',
- 'redcreek_tunneled_wins_s' => 'RedCreek-Tunneled-WINS-Server1',
- 'redcreek_tunneled_wins_t' => 'RedCreek-Tunneled-WINS-Server2',
- 'replicate_to_realm' => 'Replicate-To-Realm',
- 'reply_message' => 'Reply-Message',
- 'response_packet_type' => 'Response-Packet-Type',
- 'rewrite_rule' => 'Rewrite-Rule',
- 'sdx_service_name' => 'Sdx-Service-Name',
- 'sdx_session_volume_quota' => 'Sdx-Session-Volume-Quota',
- 'sdx_tunnel_disconnect_ca' => 'Sdx-Tunnel-Disconnect-Cause-Info',
- 'service_type' => 'Service-Type',
- 'session' => 'Session',
- 'session_error_code' => 'Session_Error_Code',
- 'session_error_codf' => 'Session-Error-Code',
- 'session_error_msg' => 'Session_Error_Msg',
- 'session_error_msh' => 'Session-Error-Msg',
- 'session_protocol' => 'session-protocol',
- 'session_timeout' => 'Session-Timeout',
- 'session_type' => 'Session-Type',
- 'shasta_service_profile' => 'Shasta-Service-Profile',
- 'shasta_user_privilege' => 'Shasta-User-Privilege',
- 'shasta_vpn_name' => 'Shasta-VPN-Name',
- 'shiva_acct_serv_switch' => 'Shiva-Acct-Serv-Switch',
- 'shiva_called_number' => 'Shiva-Called-Number',
- 'shiva_calling_number' => 'Shiva-Calling-Number',
- 'shiva_compression_type' => 'Shiva-Compression-Type',
- 'shiva_connect_reason' => 'Shiva-Connect-Reason',
- 'shiva_customer_id' => 'Shiva-Customer-Id',
- 'shiva_disconnect_reason' => 'Shiva-Disconnect-Reason',
- 'shiva_event_flags' => 'Shiva-Event-Flags',
- 'shiva_function' => 'Shiva-Function',
- 'shiva_link_protocol' => 'Shiva-Link-Protocol',
- 'shiva_link_speed' => 'Shiva-Link-Speed',
- 'shiva_links_in_bundle' => 'Shiva-Links-In-Bundle',
- 'shiva_network_protocols' => 'Shiva-Network-Protocols',
- 'shiva_session_id' => 'Shiva-Session-Id',
- 'shiva_type_of_service' => 'Shiva-Type-Of-Service',
- 'shiva_user_attributes' => 'Shiva-User-Attributes',
- 'simultaneous_use' => 'Simultaneous-Use',
- 'sip_from' => 'Sip-From',
- 'sip_hdr' => 'sip-hdr',
- 'sip_method' => 'Sip-Method',
- 'sip_to' => 'Sip-To',
- 'sip_translated_request_u' => 'Sip-Translated-Request-URI',
- 'smb_account_ctrl' => 'SMB-Account-CTRL',
- 'smb_account_ctrl_text' => 'SMB-Account-CTRL-TEXT',
- 'sonicwall_user_group' => 'SonicWall-User-Group',
- 'sonicwall_user_privilege' => 'SonicWall-User-Privilege',
- 'source_validation' => 'Source_Validation',
- 'source_validatioo' => 'Source-Validation',
- 'sql_group' => 'Sql-Group',
- 'sql_user_name' => 'SQL-User-Name',
- 'ss3_firewall_user_privil' => 'SS3-Firewall-User-Privilege',
- 'st_acct_vc_connection_id' => 'ST-Acct-VC-Connection-Id',
- 'st_policy_name' => 'ST-Policy-Name',
- 'st_primary_dns_server' => 'ST-Primary-DNS-Server',
- 'st_primary_nbns_server' => 'ST-Primary-NBNS-Server',
- 'st_secondary_dns_server' => 'ST-Secondary-DNS-Server',
- 'st_secondary_nbns_server' => 'ST-Secondary-NBNS-Server',
- 'st_service_domain' => 'ST-Service-Domain',
- 'st_service_name' => 'ST-Service-Name',
- 'state' => 'State',
- 'strip_user_name' => 'Strip-User-Name',
- 'stripped_user_name' => 'Stripped-User-Name',
- 'subscriber' => 'subscriber',
- 'suffix' => 'Suffix',
- 'telebit_accounting_info' => 'Telebit-Accounting-Info',
- 'telebit_activate_command' => 'Telebit-Activate-Command',
- 'telebit_login_command' => 'Telebit-Login-Command',
- 'telebit_port_name' => 'Telebit-Port-Name',
- 'termination_action' => 'Termination-Action',
- 'termination_menu' => 'Termination-Menu',
- 'trapeze_encryption_type' => 'Trapeze-Encryption-Type',
- 'trapeze_end_date' => 'Trapeze-End-Date',
- 'trapeze_mobility_profile' => 'Trapeze-Mobility-Profile',
- 'trapeze_ssid' => 'Trapeze-SSID',
- 'trapeze_start_date' => 'Trapeze-Start-Date',
- 'trapeze_time_of_day' => 'Trapeze-Time-Of-Day',
- 'trapeze_url' => 'Trapeze-URL',
- 'trapeze_vlan_name' => 'Trapeze-VLAN-Name',
- 'tty_level_max' => 'TTY_Level_Max',
- 'tty_level_may' => 'TTY-Level-Max',
- 'tty_level_start' => 'TTY_Level_Start',
- 'tty_level_staru' => 'TTY-Level-Start',
- 'tunnel_algorithm' => 'Tunnel_Algorithm',
- 'tunnel_algorithn' => 'Tunnel-Algorithm',
- 'tunnel_assignment_id' => 'Tunnel-Assignment-Id',
- 'tunnel_client_auth_id' => 'Tunnel-Client-Auth-Id',
- 'tunnel_client_endpoint' => 'Tunnel-Client-Endpoint',
- 'tunnel_cmd_timeout' => 'Tunnel_Cmd_Timeout',
- 'tunnel_cmd_timeouu' => 'Tunnel-Cmd-Timeout',
- 'tunnel_connection_id' => 'Tunnel-Connection-Id',
- 'tunnel_context' => 'Tunnel_Context',
- 'tunnel_contexu' => 'Tunnel-Context',
- 'tunnel_deadtime' => 'Tunnel_Deadtime',
- 'tunnel_deadtimf' => 'Tunnel-Deadtime',
- 'tunnel_dnis' => 'Tunnel_DNIS',
- 'tunnel_dnit' => 'Tunnel-DNIS',
- 'tunnel_domain' => 'Tunnel_Domain',
- 'tunnel_domaio' => 'Tunnel-Domain',
- 'tunnel_function' => 'Tunnel_Function',
- 'tunnel_functioo' => 'Tunnel-Function',
- 'tunnel_group' => 'Tunnel_Group',
- 'tunnel_grouq' => 'Tunnel-Group',
- 'tunnel_l2f_second_passwo' => 'Tunnel_L2F_Second_Password',
- 'tunnel_l2f_second_passwp' => 'Tunnel-L2F-Second-Password',
- 'tunnel_local_name' => 'Tunnel_Local_Name',
- 'tunnel_local_namf' => 'Tunnel-Local-Name',
- 'tunnel_max_sessions' => 'Tunnel_Max_Sessions',
- 'tunnel_max_sessiont' => 'Tunnel-Max-Sessions',
- 'tunnel_max_tunnels' => 'Tunnel_Max_Tunnels',
- 'tunnel_max_tunnelt' => 'Tunnel-Max-Tunnels',
- 'tunnel_medium_type' => 'Tunnel-Medium-Type',
- 'tunnel_password' => 'Tunnel-Password',
- 'tunnel_police_burst' => 'Tunnel_Police_Burst',
- 'tunnel_police_bursu' => 'Tunnel-Police-Burst',
- 'tunnel_police_rate' => 'Tunnel_Police_Rate',
- 'tunnel_police_ratf' => 'Tunnel-Police-Rate',
- 'tunnel_preference' => 'Tunnel-Preference',
- 'tunnel_private_group_id' => 'Tunnel-Private-Group-Id',
- 'tunnel_rate_limit_burst' => 'Tunnel_Rate_Limit_Burst',
- 'tunnel_rate_limit_bursu' => 'Tunnel-Rate-Limit-Burst',
- 'tunnel_rate_limit_rate' => 'Tunnel_Rate_Limit_Rate',
- 'tunnel_rate_limit_ratf' => 'Tunnel-Rate-Limit-Rate',
- 'tunnel_remote_name' => 'Tunnel_Remote_Name',
- 'tunnel_remote_namf' => 'Tunnel-Remote-Name',
- 'tunnel_retransmit' => 'Tunnel_Retransmit',
- 'tunnel_retransmiu' => 'Tunnel-Retransmit',
- 'tunnel_server_auth_id' => 'Tunnel-Server-Auth-Id',
- 'tunnel_server_endpoint' => 'Tunnel-Server-Endpoint',
- 'tunnel_session_auth' => 'Tunnel_Session_Auth',
- 'tunnel_session_auth_ctx' => 'Tunnel_Session_Auth_Ctx',
- 'tunnel_session_auth_cty' => 'Tunnel-Session-Auth-Ctx',
- 'tunnel_session_auth_serv' => 'Tunnel_Session_Auth_Service_Grp',
- 'tunnel_session_auth_serw' => 'Tunnel-Session-Auth-Service-Grp',
- 'tunnel_session_auti' => 'Tunnel-Session-Auth',
- 'tunnel_type' => 'Tunnel-Type',
- 'tunnel_window' => 'Tunnel_Window',
- 'tunnel_windox' => 'Tunnel-Window',
- 'unix_ftp_gid' => 'Unix-FTP-GID',
- 'unix_ftp_group_ids' => 'Unix-FTP-Group-Ids',
- 'unix_ftp_group_names' => 'Unix-FTP-Group-Names',
- 'unix_ftp_home' => 'Unix-FTP-Home',
- 'unix_ftp_shell' => 'Unix-FTP-Shell',
- 'unix_ftp_uid' => 'Unix-FTP-UID',
- 'user_category' => 'User-Category',
- 'user_name' => 'User-Name',
- 'user_name_is_star' => 'User-Name-Is-Star',
- 'user_password' => 'User-Password',
- 'user_profile' => 'User-Profile',
- 'user_service_type' => 'User-Service-Type',
- 'usr_accm_type' => 'USR-ACCM-Type',
- 'usr_acct_reason_code' => 'USR-Acct-Reason-Code',
- 'usr_actual_voltage' => 'USR-Actual-Voltage',
- 'usr_appletalk' => 'USR-Appletalk',
- 'usr_appletalk_network_ra' => 'USR-Appletalk-Network-Range',
- 'usr_at_call_input_filter' => 'USR-AT-Call-Input-Filter',
- 'usr_at_call_output_filte' => 'USR-AT-Call-Output-Filter',
- 'usr_at_input_filter' => 'USR-AT-Input-Filter',
- 'usr_at_output_filter' => 'USR-AT-Output-Filter',
- 'usr_at_rtmp_input_filter' => 'USR-AT-RTMP-Input-Filter',
- 'usr_at_rtmp_output_filte' => 'USR-AT-RTMP-Output-Filter',
- 'usr_at_zip_input_filter' => 'USR-AT-Zip-Input-Filter',
- 'usr_at_zip_output_filter' => 'USR-AT-Zip-Output-Filter',
- 'usr_auth_mode' => 'USR-Auth-Mode',
- 'usr_back_channel_data_ra' => 'USR-Back-Channel-Data-Rate',
- 'usr_bearer_capabilities' => 'USR-Bearer-Capabilities',
- 'usr_block_error_count_li' => 'USR-Block-Error-Count-Limit',
- 'usr_blocks_received' => 'USR-Blocks-Received',
- 'usr_blocks_resent' => 'USR-Blocks-Resent',
- 'usr_blocks_sent' => 'USR-Blocks-Sent',
- 'usr_bridging' => 'USR-Bridging',
- 'usr_call_arrival_in_gmt' => 'USR-Call-Arrival-in-GMT',
- 'usr_call_arrival_time' => 'USR-Call-Arrival-Time',
- 'usr_call_connect_in_gmt' => 'USR-Call-Connect-in-GMT',
- 'usr_call_connecting_time' => 'USR-Call-Connecting-Time',
- 'usr_call_end_date_time' => 'USR-Call-End-Date-Time',
- 'usr_call_end_time' => 'USR-Call-End-Time',
- 'usr_call_error_code' => 'USR-Call-Error-Code',
- 'usr_call_event_code' => 'USR-Call-Event-Code',
- 'usr_call_reference_numbe' => 'USR-Call-Reference-Number',
- 'usr_call_start_date_time' => 'USR-Call-Start-Date-Time',
- 'usr_call_terminate_in_gm' => 'USR-Call-Terminate-in-GMT',
- 'usr_call_type' => 'USR-Call-Type',
- 'usr_callback_type' => 'USR-Callback-Type',
- 'usr_called_party_number' => 'USR-Called-Party-Number',
- 'usr_calling_party_number' => 'USR-Calling-Party-Number',
- 'usr_card_type' => 'USR-Card-Type',
- 'usr_ccp_algorithm' => 'USR-CCP-Algorithm',
- 'usr_cdma_call_reference_' => 'USR-CDMA-Call-Reference-Number',
- 'usr_channel' => 'USR-Channel',
- 'usr_channel_connected_to' => 'USR-Channel-Connected-To',
- 'usr_channel_decrement' => 'USR-Channel-Decrement',
- 'usr_channel_expansion' => 'USR-Channel-Expansion',
- 'usr_characters_received' => 'USR-Characters-Received',
- 'usr_characters_sent' => 'USR-Characters-Sent',
- 'usr_chassis_call_channel' => 'USR-Chassis-Call-Channel',
- 'usr_chassis_call_slot' => 'USR-Chassis-Call-Slot',
- 'usr_chassis_call_span' => 'USR-Chassis-Call-Span',
- 'usr_chassis_slot' => 'USR-Chassis-Slot',
- 'usr_chassis_temp_thresho' => 'USR-Chassis-Temp-Threshold',
- 'usr_chassis_temperature' => 'USR-Chassis-Temperature',
- 'usr_chat_script_name' => 'USR-Chat-Script-Name',
- 'usr_compression_algorith' => 'USR-Compression-Algorithm',
- 'usr_compression_reset_mo' => 'USR-Compression-Reset-Mode',
- 'usr_compression_type' => 'USR-Compression-Type',
- 'usr_connect_speed' => 'USR-Connect-Speed',
- 'usr_connect_term_reason' => 'USR-Connect-Term-Reason',
- 'usr_connect_time' => 'USR-Connect-Time',
- 'usr_connect_time_limit' => 'USR-Connect-Time-Limit',
- 'usr_cusr_hat_script_rule' => 'USR-CUSR-hat-Script-Rules',
- 'usr_default_dte_data_rat' => 'USR-Default-DTE-Data-Rate',
- 'usr_device_connected_to' => 'USR-Device-Connected-To',
- 'usr_disconnect_cause_ind' => 'USR-Disconnect-Cause-Indicator',
- 'usr_dnis_reauthenticatio' => 'USR-DNIS-ReAuthentication',
- 'usr_ds0' => 'USR-DS0',
- 'usr_ds0s' => 'USR-DS0s',
- 'usr_dte_data_idle_timout' => 'USR-DTE-Data-Idle-Timout',
- 'usr_dte_ring_no_answer_l' => 'USR-DTE-Ring-No-Answer-Limit',
- 'usr_dtr_false_timeout' => 'USR-DTR-False-Timeout',
- 'usr_dtr_true_timeout' => 'USR-DTR-True-Timeout',
- 'usr_end_time' => 'USR-End-Time',
- 'usr_equalization_type' => 'USR-Equalization-Type',
- 'usr_esn' => 'USR-ESN',
- 'usr_et_bridge_call_outpu' => 'USR-ET-Bridge-Call-Output-Filte',
- 'usr_et_bridge_input_filt' => 'USR-ET-Bridge-Input-Filter',
- 'usr_et_bridge_output_fil' => 'USR-ET-Bridge-Output-Filter',
- 'usr_event_date_time' => 'USR-Event-Date-Time',
- 'usr_event_id' => 'USR-Event-Id',
- 'usr_expansion_algorithm' => 'USR-Expansion-Algorithm',
- 'usr_expected_voltage' => 'USR-Expected-Voltage',
- 'usr_failure_to_connect_r' => 'USR-Failure-to-Connect-Reason',
- 'usr_fallback_enabled' => 'USR-Fallback-Enabled',
- 'usr_fallback_limit' => 'USR-Fallback-Limit',
- 'usr_filter_zones' => 'USR-Filter-Zones',
- 'usr_final_rx_link_data_r' => 'USR-Final-Rx-Link-Data-Rate',
- 'usr_final_tx_link_data_r' => 'USR-Final-Tx-Link-Data-Rate',
- 'usr_framed_ip_address_po' => 'USR-Framed_IP_Address_Pool_Name',
- 'usr_framed_ipx_route' => 'USR-Framed-IPX-Route',
- 'usr_gateway_ip_address' => 'USR-Gateway-IP-Address',
- 'usr_harc_disconnect_code' => 'USR-HARC-Disconnect-Code',
- 'usr_host_type' => 'USR-Host-Type',
- 'usr_ids0_call_type' => 'USR-IDS0-Call-Type',
- 'usr_igmp_maximum_respons' => 'USR-IGMP-Maximum-Response-Time',
- 'usr_igmp_query_interval' => 'USR-IGMP-Query-Interval',
- 'usr_igmp_robustness' => 'USR-IGMP-Robustness',
- 'usr_igmp_routing' => 'USR-IGMP-Routing',
- 'usr_igmp_version' => 'USR-IGMP-Version',
- 'usr_imsi' => 'USR-IMSI',
- 'usr_initial_rx_link_data' => 'USR-Initial-Rx-Link-Data-Rate',
- 'usr_initial_tx_link_data' => 'USR-Initial-Tx-Link-Data-Rate',
- 'usr_interface_index' => 'USR-Interface-Index',
- 'usr_ip' => 'USR-IP',
- 'usr_ip_call_input_filter' => 'USR-IP-Call-Input-Filter',
- 'usr_ip_call_output_filte' => 'USR-IP-Call-Output-Filter',
- 'usr_ip_default_route_opt' => 'USR-IP-Default-Route-Option',
- 'usr_ip_rip_input_filter' => 'USR-IP-RIP-Input-Filter',
- 'usr_ip_rip_output_filter' => 'USR-IP-RIP-Output-Filter',
- 'usr_ip_rip_policies' => 'USR-IP-RIP-Policies',
- 'usr_ip_rip_simple_auth_p' => 'USR-IP-RIP-Simple-Auth-Password',
- 'usr_ip_saa_filter' => 'USR-IP-SAA-Filter',
- 'usr_ipx' => 'USR-IPX',
- 'usr_ipx_call_input_filte' => 'USR-IPX-Call-Input-Filter',
- 'usr_ipx_call_output_filt' => 'USR-IPX-Call-Output-Filter',
- 'usr_ipx_rip_input_filter' => 'USR-IPX-RIP-Input-Filter',
- 'usr_ipx_rip_output_filte' => 'USR-IPX-RIP-Output-Filter',
- 'usr_ipx_routing' => 'USR-IPX-Routing',
- 'usr_ipx_wan' => 'USR-IPX-WAN',
- 'usr_iwf_call_identifier' => 'USR-IWF-Call-Identifier',
- 'usr_iwf_ip_address' => 'USR-IWF-IP-Address',
- 'usr_keypress_timeout' => 'USR-Keypress-Timeout',
- 'usr_last_callers_number_' => 'USR-Last-Callers-Number-ANI',
- 'usr_last_number_dialed_i' => 'USR-Last-Number-Dialed-In-DNIS',
- 'usr_last_number_dialed_o' => 'USR-Last-Number-Dialed-Out',
- 'usr_line_reversals' => 'USR-Line-Reversals',
- 'usr_local_framed_ip_addr' => 'USR-Local-Framed-IP-Addr',
- 'usr_local_ip_address' => 'USR-Local-IP-Address',
- 'usr_log_filter_packets' => 'USR-Log-Filter-Packets',
- 'usr_max_channels' => 'USR-Max-Channels',
- 'usr_mbi_ct_bchannel_used' => 'USR-Mbi_Ct_BChannel_Used',
- 'usr_mbi_ct_pri_card_slot' => 'USR-Mbi_Ct_PRI_Card_Slot',
- 'usr_mbi_ct_pri_card_span' => 'USR-Mbi_Ct_PRI_Card_Span_Line',
- 'usr_mbi_ct_tdm_time_slot' => 'USR-Mbi_Ct_TDM_Time_Slot',
- 'usr_mic' => 'USR-MIC',
- 'usr_min_compression_size' => 'USR-Min-Compression-Size',
- 'usr_mobile_ip_address' => 'USR-Mobile-IP-Address',
- 'usr_mobile_numbytes_rxed' => 'USR-Mobile-NumBytes-Rxed',
- 'usr_mobile_numbytes_txed' => 'USR-Mobile-NumBytes-Txed',
- 'usr_mobileip_home_agent_' => 'USR-MobileIP-Home-Agent-Address',
- 'usr_modem_group' => 'USR-Modem-Group',
- 'usr_modem_setup_time' => 'USR-Modem-Setup-Time',
- 'usr_modem_training_time' => 'USR-Modem-Training-Time',
- 'usr_modulation_type' => 'USR-Modulation-Type',
- 'usr_mp_edo' => 'USR-MP-EDO',
- 'usr_mp_edo_hiper' => 'USR-MP-EDO-HIPER',
- 'usr_mp_mrru' => 'USR-MP-MRRU',
- 'usr_mpip_tunnel_originat' => 'USR-MPIP-Tunnel-Originator',
- 'usr_multicast_forwarding' => 'USR-Multicast-Forwarding',
- 'usr_multicast_proxy' => 'USR-Multicast-Proxy',
- 'usr_multicast_receive' => 'USR-Multicast-Receive',
- 'usr_nas_type' => 'USR-NAS-Type',
- 'usr_nfas_id' => 'USR-NFAS-ID',
- 'usr_num_fax_pages_proces' => 'USR-Num-Fax-Pages-Processed',
- 'usr_number_of_blers' => 'USR-Number-of-Blers',
- 'usr_number_of_characters' => 'USR-Number-Of-Characters-Lost',
- 'usr_number_of_fallbacks' => 'USR-Number-of-Fallbacks',
- 'usr_number_of_link_naks' => 'USR-Number-of-Link-NAKs',
- 'usr_number_of_link_timeo' => 'USR-Number-of-Link-Timeouts',
- 'usr_number_of_rings_limi' => 'USR-Number-of-Rings-Limit',
- 'usr_number_of_upshifts' => 'USR-Number-of-Upshifts',
- 'usr_orig_nas_type' => 'USR-Orig-NAS-Type',
- 'usr_originate_answer_mod' => 'USR-Originate-Answer-Mode',
- 'usr_ospf_addressless_ind' => 'USR-OSPF-Addressless-Index',
- 'usr_packet_bus_session' => 'USR-Packet-Bus-Session',
- 'usr_physical_state' => 'USR-Physical-State',
- 'usr_port_tap' => 'USR-Port-Tap',
- 'usr_port_tap_address' => 'USR-Port-Tap-Address',
- 'usr_port_tap_facility' => 'USR-Port-Tap-Facility',
- 'usr_port_tap_format' => 'USR-Port-Tap-Format',
- 'usr_port_tap_output' => 'USR-Port-Tap-Output',
- 'usr_port_tap_priority' => 'USR-Port-Tap-Priority',
- 'usr_power_supply_number' => 'USR-Power-Supply-Number',
- 'usr_primary_dns_server' => 'USR-Primary_DNS_Server',
- 'usr_primary_nbns_server' => 'USR-Primary_NBNS_Server',
- 'usr_pw_cutoff' => 'USR-PW_Cutoff',
- 'usr_pw_framed_routing_v2' => 'USR-PW_Framed_Routing_V2',
- 'usr_pw_index' => 'USR-PW_Index',
- 'usr_pw_packet' => 'USR-PW_Packet',
- 'usr_pw_tunnel_authentica' => 'USR-PW_Tunnel_Authentication',
- 'usr_pw_usr_ifilter_ip' => 'USR-PW_USR_IFilter_IP',
- 'usr_pw_usr_ifilter_ipx' => 'USR-PW_USR_IFilter_IPX',
- 'usr_pw_usr_ofilter_ip' => 'USR-PW_USR_OFilter_IP',
- 'usr_pw_usr_ofilter_ipx' => 'USR-PW_USR_OFilter_IPX',
- 'usr_pw_usr_ofilter_sap' => 'USR-PW_USR_OFilter_SAP',
- 'usr_pw_vpn_gateway' => 'USR-PW_VPN_Gateway',
- 'usr_pw_vpn_id' => 'USR-PW_VPN_ID',
- 'usr_pw_vpn_name' => 'USR-PW_VPN_Name',
- 'usr_pw_vpn_neighbor' => 'USR-PW_VPN_Neighbor',
- 'usr_q931_call_reference_' => 'USR-Q931-Call-Reference-Value',
- 'usr_rad_dvmrp_metric' => 'USR-Rad-Dvmrp-Metric',
- 'usr_rad_location_type' => 'USR-Rad-Location-Type',
- 'usr_rad_multicast_routin' => 'USR-Rad-Multicast-Routing-Ttl',
- 'usr_rad_multicast_routio' => 'USR-Rad-Multicast-Routing-RtLim',
- 'usr_rad_multicast_routip' => 'USR-Rad-Multicast-Routing-Proto',
- 'usr_rad_multicast_routiq' => 'USR-Rad-Multicast-Routing-Bound',
- 'usr_re_chap_timeout' => 'USR-Re-Chap-Timeout',
- 'usr_receive_acc_map' => 'USR-Receive-Acc-Map',
- 'usr_reply_script1' => 'USR-Reply-Script1',
- 'usr_reply_script2' => 'USR-Reply-Script2',
- 'usr_reply_script3' => 'USR-Reply-Script3',
- 'usr_reply_script4' => 'USR-Reply-Script4',
- 'usr_reply_script5' => 'USR-Reply-Script5',
- 'usr_reply_script6' => 'USR-Reply-Script6',
- 'usr_request_type' => 'USR-Request-Type',
- 'usr_retrains_granted' => 'USR-Retrains-Granted',
- 'usr_retrains_requested' => 'USR-Retrains-Requested',
- 'usr_rmmie_firmware_build' => 'USR-RMMIE-Firmware-Build-Date',
- 'usr_rmmie_firmware_versi' => 'USR-RMMIE-Firmware-Version',
- 'usr_rmmie_last_update_ev' => 'USR-RMMIE-Last-Update-Event',
- 'usr_rmmie_last_update_ti' => 'USR-RMMIE-Last-Update-Time',
- 'usr_rmmie_manufacturer_i' => 'USR-RMMIE-Manufacturer-ID',
- 'usr_rmmie_num_of_updates' => 'USR-RMMIE-Num-Of-Updates',
- 'usr_rmmie_planned_discon' => 'USR-RMMIE-Planned-Disconnect',
- 'usr_rmmie_product_code' => 'USR-RMMIE-Product-Code',
- 'usr_rmmie_pwrlvl_farecho' => 'USR-RMMIE-PwrLvl-FarEcho-Canc',
- 'usr_rmmie_pwrlvl_nearech' => 'USR-RMMIE-PwrLvl-NearEcho-Canc',
- 'usr_rmmie_pwrlvl_noise_l' => 'USR-RMMIE-PwrLvl-Noise-Lvl',
- 'usr_rmmie_pwrlvl_xmit_lv' => 'USR-RMMIE-PwrLvl-Xmit-Lvl',
- 'usr_rmmie_rcv_pwrlvl_330' => 'USR-RMMIE-Rcv-PwrLvl-3300Hz',
- 'usr_rmmie_rcv_pwrlvl_375' => 'USR-RMMIE-Rcv-PwrLvl-3750Hz',
- 'usr_rmmie_rcv_tot_pwrlvl' => 'USR-RMMIE-Rcv-Tot-PwrLvl',
- 'usr_rmmie_serial_number' => 'USR-RMMIE-Serial-Number',
- 'usr_rmmie_status' => 'USR-RMMIE-Status',
- 'usr_rmmie_x2_status' => 'USR-RMMIE-x2-Status',
- 'usr_routing_protocol' => 'USR-Routing-Protocol',
- 'usr_sap_filter_in' => 'USR-SAP-Filter-In',
- 'usr_secondary_dns_server' => 'USR-Secondary_DNS_Server',
- 'usr_secondary_nbns_serve' => 'USR-Secondary_NBNS_Server',
- 'usr_security_login_limit' => 'USR-Security-Login-Limit',
- 'usr_security_resp_limit' => 'USR-Security-Resp-Limit',
- 'usr_send_name' => 'USR-Send-Name',
- 'usr_send_password' => 'USR-Send-Password',
- 'usr_send_script1' => 'USR-Send-Script1',
- 'usr_send_script2' => 'USR-Send-Script2',
- 'usr_send_script3' => 'USR-Send-Script3',
- 'usr_send_script4' => 'USR-Send-Script4',
- 'usr_send_script5' => 'USR-Send-Script5',
- 'usr_send_script6' => 'USR-Send-Script6',
- 'usr_server_time' => 'USR-Server-Time',
- 'usr_service_option' => 'USR-Service-Option',
- 'usr_simplified_mnp_level' => 'USR-Simplified-MNP-Levels',
- 'usr_simplified_v42bis_us' => 'USR-Simplified-V42bis-Usage',
- 'usr_slot_connected_to' => 'USR-Slot-Connected-To',
- 'usr_speed_of_connection' => 'USR-Speed-Of-Connection',
- 'usr_spoofing' => 'USR-Spoofing',
- 'usr_start_time' => 'USR-Start-Time',
- 'usr_supports_tags' => 'USR-Supports-Tags',
- 'usr_sync_async_mode' => 'USR-Sync-Async-Mode',
- 'usr_syslog_tap' => 'USR-Syslog-Tap',
- 'usr_terminal_type' => 'USR-Terminal-Type',
- 'usr_transmit_acc_map' => 'USR-Transmit-Acc-Map',
- 'usr_tunnel_auth_hostname' => 'USR-Tunnel-Auth-Hostname',
- 'usr_tunnel_security' => 'USR-Tunnel-Security',
- 'usr_tunnel_switch_endpoi' => 'USR-Tunnel-Switch-Endpoint',
- 'usr_tunneled_mlpp' => 'USR-Tunneled-MLPP',
- 'usr_unauthenticated_time' => 'USR-Unauthenticated-Time',
- 'usr_vpn_encrypter' => 'USR-VPN-Encrypter',
- 'usr_vpn_gw_location_id' => 'USR-VPN-GW-Location-Id',
- 'usr_vts_session_key' => 'USR-VTS-Session-Key',
- 'vendor_specific' => 'Vendor-Specific',
- 'versanet_termination_cau' => 'Versanet-Termination-Cause',
- 'vnc_pppoe_cbq_rx' => 'VNC-PPPoE-CBQ-RX',
- 'vnc_pppoe_cbq_rx_fallbac' => 'VNC-PPPoE-CBQ-RX-Fallback',
- 'vnc_pppoe_cbq_tx' => 'VNC-PPPoE-CBQ-TX',
- 'vnc_pppoe_cbq_tx_fallbac' => 'VNC-PPPoE-CBQ-TX-Fallback',
- 'vnc_splash' => 'VNC-Splash',
- 'wispr_bandwidth_max_down' => 'WISPr-Bandwidth-Max-Down',
- 'wispr_bandwidth_max_up' => 'WISPr-Bandwidth-Max-Up',
- 'wispr_bandwidth_min_down' => 'WISPr-Bandwidth-Min-Down',
- 'wispr_bandwidth_min_up' => 'WISPr-Bandwidth-Min-Up',
- 'wispr_billing_class_of_s' => 'WISPr-Billing-Class-Of-Service',
- 'wispr_location_id' => 'WISPr-Location-ID',
- 'wispr_location_name' => 'WISPr-Location-Name',
- 'wispr_logoff_url' => 'WISPr-Logoff-URL',
- 'wispr_redirection_url' => 'WISPr-Redirection-URL',
- 'wispr_session_terminate_' => 'WISPr-Session-Terminate-Time',
- 'wispr_session_terminatea' => 'WISPr-Session-Terminate-End-Of-Day',
- 'x_ascend_add_seconds' => 'X-Ascend-Add-Seconds',
- 'x_ascend_ara_pw' => 'X-Ascend-Ara-PW',
- 'x_ascend_assign_ip_clien' => 'X-Ascend-Assign-IP-Client',
- 'x_ascend_assign_ip_globa' => 'X-Ascend-Assign-IP-Global-Pool',
- 'x_ascend_assign_ip_pool' => 'X-Ascend-Assign-IP-Pool',
- 'x_ascend_assign_ip_serve' => 'X-Ascend-Assign-IP-Server',
- 'x_ascend_authen_alias' => 'X-Ascend-Authen-Alias',
- 'x_ascend_backup' => 'X-Ascend-Backup',
- 'x_ascend_bacp_enable' => 'X-Ascend-BACP-Enable',
- 'x_ascend_base_channel_co' => 'X-Ascend-Base-Channel-Count',
- 'x_ascend_billing_number' => 'X-Ascend-Billing-Number',
- 'x_ascend_bridge' => 'X-Ascend-Bridge',
- 'x_ascend_bridge_address' => 'X-Ascend-Bridge-Address',
- 'x_ascend_call_attempt_li' => 'X-Ascend-Call-Attempt-Limit',
- 'x_ascend_call_block_dura' => 'X-Ascend-Call-Block-Duration',
- 'x_ascend_call_by_call' => 'X-Ascend-Call-By-Call',
- 'x_ascend_call_filter' => 'X-Ascend-Call-Filter',
- 'x_ascend_call_type' => 'X-Ascend-Call-Type',
- 'x_ascend_callback' => 'X-Ascend-Callback',
- 'x_ascend_client_assign_d' => 'X-Ascend-Client-Assign-DNS',
- 'x_ascend_client_gateway' => 'X-Ascend-Client-Gateway',
- 'x_ascend_client_primary_' => 'X-Ascend-Client-Primary-DNS',
- 'x_ascend_client_secondar' => 'X-Ascend-Client-Secondary-DNS',
- 'x_ascend_connect_progres' => 'X-Ascend-Connect-Progress',
- 'x_ascend_data_filter' => 'X-Ascend-Data-Filter',
- 'x_ascend_data_rate' => 'X-Ascend-Data-Rate',
- 'x_ascend_data_svc' => 'X-Ascend-Data-Svc',
- 'x_ascend_dba_monitor' => 'X-Ascend-DBA-Monitor',
- 'x_ascend_dec_channel_cou' => 'X-Ascend-Dec-Channel-Count',
- 'x_ascend_dhcp_maximum_le' => 'X-Ascend-DHCP-Maximum-Leases',
- 'x_ascend_dhcp_pool_numbe' => 'X-Ascend-DHCP-Pool-Number',
- 'x_ascend_dhcp_reply' => 'X-Ascend-DHCP-Reply',
- 'x_ascend_dial_number' => 'X-Ascend-Dial-Number',
- 'x_ascend_dialout_allowed' => 'X-Ascend-Dialout-Allowed',
- 'x_ascend_disconnect_caus' => 'X-Ascend-Disconnect-Cause',
- 'x_ascend_event_type' => 'X-Ascend-Event-Type',
- 'x_ascend_expect_callback' => 'X-Ascend-Expect-Callback',
- 'x_ascend_fcp_parameter' => 'X-Ascend-FCP-Parameter',
- 'x_ascend_first_dest' => 'X-Ascend-First-Dest',
- 'x_ascend_force_56' => 'X-Ascend-Force-56',
- 'x_ascend_fr_circuit_name' => 'X-Ascend-FR-Circuit-Name',
- 'x_ascend_fr_dce_n392' => 'X-Ascend-FR-DCE-N392',
- 'x_ascend_fr_dce_n393' => 'X-Ascend-FR-DCE-N393',
- 'x_ascend_fr_direct' => 'X-Ascend-FR-Direct',
- 'x_ascend_fr_direct_dlci' => 'X-Ascend-FR-Direct-DLCI',
- 'x_ascend_fr_direct_profi' => 'X-Ascend-FR-Direct-Profile',
- 'x_ascend_fr_dlci' => 'X-Ascend-FR-DLCI',
- 'x_ascend_fr_dte_n392' => 'X-Ascend-FR-DTE-N392',
- 'x_ascend_fr_dte_n393' => 'X-Ascend-FR-DTE-N393',
- 'x_ascend_fr_link_mgt' => 'X-Ascend-FR-Link-Mgt',
- 'x_ascend_fr_linkup' => 'X-Ascend-FR-LinkUp',
- 'x_ascend_fr_n391' => 'X-Ascend-FR-N391',
- 'x_ascend_fr_nailed_grp' => 'X-Ascend-FR-Nailed-Grp',
- 'x_ascend_fr_profile_name' => 'X-Ascend-FR-Profile-Name',
- 'x_ascend_fr_t391' => 'X-Ascend-FR-T391',
- 'x_ascend_fr_t392' => 'X-Ascend-FR-T392',
- 'x_ascend_fr_type' => 'X-Ascend-FR-Type',
- 'x_ascend_ft1_caller' => 'X-Ascend-FT1-Caller',
- 'x_ascend_group' => 'X-Ascend-Group',
- 'x_ascend_handle_ipx' => 'X-Ascend-Handle-IPX',
- 'x_ascend_history_weigh_t' => 'X-Ascend-History-Weigh-Type',
- 'x_ascend_home_agent_ip_a' => 'X-Ascend-Home-Agent-IP-Addr',
- 'x_ascend_home_agent_pass' => 'X-Ascend-Home-Agent-Password',
- 'x_ascend_home_agent_udp_' => 'X-Ascend-Home-Agent-UDP-Port',
- 'x_ascend_home_network_na' => 'X-Ascend-Home-Network-Name',
- 'x_ascend_host_info' => 'X-Ascend-Host-Info',
- 'x_ascend_idle_limit' => 'X-Ascend-Idle-Limit',
- 'x_ascend_if_netmask' => 'X-Ascend-IF-Netmask',
- 'x_ascend_inc_channel_cou' => 'X-Ascend-Inc-Channel-Count',
- 'x_ascend_ip_direct' => 'X-Ascend-IP-Direct',
- 'x_ascend_ip_pool_definit' => 'X-Ascend-IP-Pool-Definition',
- 'x_ascend_ipx_alias' => 'X-Ascend-IPX-Alias',
- 'x_ascend_ipx_node_addr' => 'X-Ascend-IPX-Node-Addr',
- 'x_ascend_ipx_peer_mode' => 'X-Ascend-IPX-Peer-Mode',
- 'x_ascend_ipx_route' => 'X-Ascend-IPX-Route',
- 'x_ascend_link_compressio' => 'X-Ascend-Link-Compression',
- 'x_ascend_maximum_call_du' => 'X-Ascend-Maximum-Call-Duration',
- 'x_ascend_maximum_channel' => 'X-Ascend-Maximum-Channels',
- 'x_ascend_maximum_time' => 'X-Ascend-Maximum-Time',
- 'x_ascend_menu_item' => 'X-Ascend-Menu-Item',
- 'x_ascend_menu_selector' => 'X-Ascend-Menu-Selector',
- 'x_ascend_metric' => 'X-Ascend-Metric',
- 'x_ascend_minimum_channel' => 'X-Ascend-Minimum-Channels',
- 'x_ascend_modem_portno' => 'X-Ascend-Modem-PortNo',
- 'x_ascend_modem_shelfno' => 'X-Ascend-Modem-ShelfNo',
- 'x_ascend_modem_slotno' => 'X-Ascend-Modem-SlotNo',
- 'x_ascend_mpp_idle_percen' => 'X-Ascend-MPP-Idle-Percent',
- 'x_ascend_multicast_clien' => 'X-Ascend-Multicast-Client',
- 'x_ascend_multicast_rate_' => 'X-Ascend-Multicast-Rate-Limit',
- 'x_ascend_multilink_id' => 'X-Ascend-Multilink-ID',
- 'x_ascend_netware_timeout' => 'X-Ascend-Netware-timeout',
- 'x_ascend_num_in_multilin' => 'X-Ascend-Num-In-Multilink',
- 'x_ascend_number_sessions' => 'X-Ascend-Number-Sessions',
- 'x_ascend_ppp_address' => 'X-Ascend-PPP-Address',
- 'x_ascend_ppp_async_map' => 'X-Ascend-PPP-Async-Map',
- 'x_ascend_ppp_vj_1172' => 'X-Ascend-PPP-VJ-1172',
- 'x_ascend_ppp_vj_slot_com' => 'X-Ascend-PPP-VJ-Slot-Comp',
- 'x_ascend_pre_input_octet' => 'X-Ascend-Pre-Input-Octets',
- 'x_ascend_pre_input_packe' => 'X-Ascend-Pre-Input-Packets',
- 'x_ascend_pre_output_octe' => 'X-Ascend-Pre-Output-Octets',
- 'x_ascend_pre_output_pack' => 'X-Ascend-Pre-Output-Packets',
- 'x_ascend_preempt_limit' => 'X-Ascend-Preempt-Limit',
- 'x_ascend_presession_time' => 'X-Ascend-PreSession-Time',
- 'x_ascend_pri_number_type' => 'X-Ascend-PRI-Number-Type',
- 'x_ascend_primary_home_ag' => 'X-Ascend-Primary-Home-Agent',
- 'x_ascend_pw_lifetime' => 'X-Ascend-PW-Lifetime',
- 'x_ascend_pw_warntime' => 'X-Ascend-PW-Warntime',
- 'x_ascend_receive_secret' => 'X-Ascend-Receive-Secret',
- 'x_ascend_remote_addr' => 'X-Ascend-Remote-Addr',
- 'x_ascend_remove_seconds' => 'X-Ascend-Remove-Seconds',
- 'x_ascend_require_auth' => 'X-Ascend-Require-Auth',
- 'x_ascend_route_ip' => 'X-Ascend-Route-IP',
- 'x_ascend_route_ipx' => 'X-Ascend-Route-IPX',
- 'x_ascend_secondary_home_' => 'X-Ascend-Secondary-Home-Agent',
- 'x_ascend_seconds_of_hist' => 'X-Ascend-Seconds-Of-History',
- 'x_ascend_send_auth' => 'X-Ascend-Send-Auth',
- 'x_ascend_send_passwd' => 'X-Ascend-Send-Passwd',
- 'x_ascend_send_secret' => 'X-Ascend-Send-Secret',
- 'x_ascend_session_svr_key' => 'X-Ascend-Session-Svr-Key',
- 'x_ascend_shared_profile_' => 'X-Ascend-Shared-Profile-Enable',
- 'x_ascend_target_util' => 'X-Ascend-Target-Util',
- 'x_ascend_temporary_rtes' => 'X-Ascend-Temporary-Rtes',
- 'x_ascend_third_prompt' => 'X-Ascend-Third-Prompt',
- 'x_ascend_token_expiry' => 'X-Ascend-Token-Expiry',
- 'x_ascend_token_idle' => 'X-Ascend-Token-Idle',
- 'x_ascend_token_immediate' => 'X-Ascend-Token-Immediate',
- 'x_ascend_transit_number' => 'X-Ascend-Transit-Number',
- 'x_ascend_ts_idle_limit' => 'X-Ascend-TS-Idle-Limit',
- 'x_ascend_ts_idle_mode' => 'X-Ascend-TS-Idle-Mode',
- 'x_ascend_tunneling_proto' => 'X-Ascend-Tunneling-Protocol',
- 'x_ascend_user_acct_base' => 'X-Ascend-User-Acct-Base',
- 'x_ascend_user_acct_host' => 'X-Ascend-User-Acct-Host',
- 'x_ascend_user_acct_key' => 'X-Ascend-User-Acct-Key',
- 'x_ascend_user_acct_port' => 'X-Ascend-User-Acct-Port',
- 'x_ascend_user_acct_time' => 'X-Ascend-User-Acct-Time',
- 'x_ascend_user_acct_type' => 'X-Ascend-User-Acct-Type',
- 'x_ascend_xmit_rate' => 'X-Ascend-Xmit-Rate',
- 'xedia_address_pool' => 'Xedia-Address-Pool',
- 'xedia_client_access_netw' => 'Xedia-Client-Access-Network',
- 'xedia_dns_server' => 'Xedia-DNS-Server',
- 'xedia_netbios_server' => 'Xedia-NetBios-Server',
- 'xedia_ppp_echo_interval' => 'Xedia-PPP-Echo-Interval',
- 'xedia_ssh_privileges' => 'Xedia-SSH-Privileges',
-
- #NETC.NET.AU (RADIATOR?)
- 'authentication_type' => 'Authentication-Type',
-
- #wtxs (dunno)
- #'radius_operator' => 'Radius-Operator',
-
-);
-
-1;
diff --git a/FS/FS/radius_usergroup.pm b/FS/FS/radius_usergroup.pm
deleted file mode 100644
index 9bba057..0000000
--- a/FS/FS/radius_usergroup.pm
+++ /dev/null
@@ -1,131 +0,0 @@
-package FS::radius_usergroup;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::svc_acct;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::radius_usergroup - Object methods for radius_usergroup records
-
-=head1 SYNOPSIS
-
- use FS::radius_usergroup;
-
- $record = new FS::radius_usergroup \%hash;
- $record = new FS::radius_usergroup { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::radius_usergroup object links an account (see L<FS::svc_acct>) with a
-RADIUS group. FS::radius_usergroup inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item usergroupnum - primary key
-
-=item svcnum - Account (see L<FS::svc_acct>).
-
-=item groupname - group name
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new record. To add the record to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'radius_usergroup'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-#inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-#inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-#inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('usergroupnum')
- || $self->ut_number('svcnum')
- || $self->ut_foreign_key('svcnum','svc_acct','svcnum')
- || $self->ut_text('groupname')
- || $self->SUPER::check
- ;
-}
-
-=item svc_acct
-
-Returns the account associated with this record (see L<FS::svc_acct>).
-
-=cut
-
-sub svc_acct {
- my $self = shift;
- qsearchs('svc_acct', { svcnum => $self->svcnum } );
-}
-
-=back
-
-=head1 BUGS
-
-Don't let 'em get you down.
-
-=head1 SEE ALSO
-
-L<svc_acct>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/rate.pm b/FS/FS/rate.pm
deleted file mode 100644
index c50ca04..0000000
--- a/FS/FS/rate.pm
+++ /dev/null
@@ -1,379 +0,0 @@
-package FS::rate;
-
-use strict;
-use vars qw( @ISA $DEBUG );
-use FS::Record qw( qsearch qsearchs dbh fields );
-use FS::rate_detail;
-
-@ISA = qw(FS::Record);
-
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::rate - Object methods for rate records
-
-=head1 SYNOPSIS
-
- use FS::rate;
-
- $record = new FS::rate \%hash;
- $record = new FS::rate { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::rate object represents an rate plan. FS::rate inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item ratenum - primary key
-
-=item ratename
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new rate plan. To add the rate plan to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'rate'; }
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-Currently available options are: I<rate_detail>
-
-If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
-objects will have their ratenum field set and will be inserted after this
-record.
-
-=cut
-
-sub insert {
- my $self = shift;
- my %options = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->check;
- return $error if $error;
-
- $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $options{'rate_detail'} ) {
-
- my( $num, $last, $min_sec ) = (0, time, 5); #progressbar foo
-
- foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
-
- $rate_detail->ratenum($self->ratenum);
- $error = $rate_detail->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $options{'job'} ) {
- $num++;
- if ( time - $min_sec > $last ) {
- my $error = $options{'job'}->update_statustext(
- int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $last = time;
- }
- }
-
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD [ , OPTION => VALUE ... ]
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-Currently available options are: I<rate_detail>
-
-If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
-objects will have their ratenum field set and will be inserted after this
-record. Any existing rate_detail records associated with this record will be
-deleted.
-
-=cut
-
-sub replace {
- my ($new, $old) = (shift, shift);
- my %options = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
-# my @old_rate_detail = ();
-# @old_rate_detail = $old->rate_detail if $options{'rate_detail'};
-
- my $error = $new->SUPER::replace($old);
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
-# foreach my $old_rate_detail ( @old_rate_detail ) {
-#
-# my $error = $old_rate_detail->delete;
-# if ($error) {
-# $dbh->rollback if $oldAutoCommit;
-# return $error;
-# }
-#
-# if ( $options{'job'} ) {
-# $num++;
-# if ( time - $min_sec > $last ) {
-# my $error = $options{'job'}->update_statustext(
-# int( 50 * $num / scalar( @old_rate_detail ) )
-# );
-# if ( $error ) {
-# $dbh->rollback if $oldAutoCommit;
-# return $error;
-# }
-# $last = time;
-# }
-# }
-#
-# }
- if ( $options{'rate_detail'} ) {
- my $sth = $dbh->prepare('DELETE FROM rate_detail WHERE ratenum = ?') or do {
- $dbh->rollback if $oldAutoCommit;
- return $dbh->errstr;
- };
-
- $sth->execute($old->ratenum) or do {
- $dbh->rollback if $oldAutoCommit;
- return $sth->errstr;
- };
-
- my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
-# $num = 0;
- foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
-
- $rate_detail->ratenum($new->ratenum);
- $error = $rate_detail->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $options{'job'} ) {
- $num++;
- if ( time - $min_sec > $last ) {
- my $error = $options{'job'}->update_statustext(
- int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $last = time;
- }
- }
-
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid rate plan. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('ratenum')
- || $self->ut_text('ratename')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item dest_detail REGIONNUM | RATE_REGION_OBJECTD
-
-Returns the rate detail (see L<FS::rate_detail>) for this rate to the
-specificed destination.
-
-=cut
-
-sub dest_detail {
- my $self = shift;
- my $regionnum = ref($_[0]) ? shift->regionnum : shift;
- qsearchs( 'rate_detail', { 'ratenum' => $self->ratenum,
- 'dest_regionnum' => $regionnum, } );
-}
-
-=item rate_detail
-
-Returns all region-specific details (see L<FS::rate_detail>) for this rate.
-
-=cut
-
-sub rate_detail {
- my $self = shift;
- qsearch( 'rate_detail', { 'ratenum' => $self->ratenum } );
-}
-
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item process
-
-Experimental job-queue processor for web interface adds/edits
-
-=cut
-
-use Storable qw(thaw);
-use Data::Dumper;
-use MIME::Base64;
-sub process {
- my $job = shift;
-
- my $param = thaw(decode_base64(shift));
- warn Dumper($param) if $DEBUG;
-
- my $old = qsearchs('rate', { 'ratenum' => $param->{'ratenum'} } )
- if $param->{'ratenum'};
-
- my @rate_detail = map {
-
- my $regionnum = $_->regionnum;
- if ( $param->{"sec_granularity$regionnum"} ) {
-
- new FS::rate_detail {
- 'dest_regionnum' => $regionnum,
- map { $_ => $param->{"$_$regionnum"} }
- qw( min_included min_charge sec_granularity )
- };
-
- } else {
-
- new FS::rate_detail {
- 'dest_regionnum' => $regionnum,
- 'min_included' => 0,
- 'min_charge' => 0,
- 'sec_granularity' => '60'
- };
-
- }
-
- } qsearch('rate_region', {} );
-
- my $rate = new FS::rate {
- map { $_ => $param->{$_} }
- fields('rate')
- };
-
- my $error = '';
- if ( $param->{'ratenum'} ) {
- warn "$rate replacing $old (". $param->{'ratenum'}. ")\n" if $DEBUG;
- $error = $rate->replace( $old,
- 'rate_detail' => \@rate_detail,
- 'job' => $job,
- );
- } else {
- warn "inserting $rate\n" if $DEBUG;
- $error = $rate->insert( 'rate_detail' => \@rate_detail,
- 'job' => $job,
- );
- #$ratenum = $rate->getfield('ratenum');
- }
-
- die "$error\n" if $error;
-
-}
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/rate_detail.pm b/FS/FS/rate_detail.pm
deleted file mode 100644
index ad41b40..0000000
--- a/FS/FS/rate_detail.pm
+++ /dev/null
@@ -1,202 +0,0 @@
-package FS::rate_detail;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::rate;
-use FS::rate_region;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::rate_detail - Object methods for rate_detail records
-
-=head1 SYNOPSIS
-
- use FS::rate_detail;
-
- $record = new FS::rate_detail \%hash;
- $record = new FS::rate_detail { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::rate_detail object represents an call plan rate. FS::rate_detail
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item ratedetailnum - primary key
-
-=item ratenum - rate plan (see L<FS::rate>)
-
-=item orig_regionnum - call origination region
-
-=item dest_regionnum - call destination region
-
-=item min_included - included minutes
-
-=item min_charge - charge per minute
-
-=item sec_granularity - granularity in seconds, i.e. 6 or 60; 0 for per-call
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new call plan rate. To add the call plan rate to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'rate_detail'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid call plan rate. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('ratedetailnum')
- || $self->ut_foreign_key('ratenum', 'rate', 'ratenum')
- || $self->ut_foreign_keyn('orig_regionnum', 'rate_region', 'regionnum' )
- || $self->ut_foreign_key('dest_regionnum', 'rate_region', 'regionnum' )
- || $self->ut_number('min_included')
-
- #|| $self->ut_money('min_charge')
- #good enough for now...
- || $self->ut_float('min_charge')
-
- || $self->ut_number('sec_granularity')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item rate
-
-Returns the parent call plan (see L<FS::rate>) associated with this call plan
-rate.
-
-=cut
-
-sub rate {
- my $self = shift;
- qsearchs('rate', { 'ratenum' => $self->ratenum } );
-}
-
-=item orig_region
-
-Returns the origination region (see L<FS::rate_region>) associated with this
-call plan rate.
-
-=cut
-
-sub orig_region {
- my $self = shift;
- qsearchs('rate_region', { 'regionnum' => $self->orig_regionnum } );
-}
-
-=item dest_region
-
-Returns the destination region (see L<FS::rate_region>) associated with this
-call plan rate.
-
-=cut
-
-sub dest_region {
- my $self = shift;
- qsearchs('rate_region', { 'regionnum' => $self->dest_regionnum } );
-}
-
-=item dest_regionname
-
-Returns the name of the destination region (see L<FS::rate_region>) associated
-with this call plan rate.
-
-=cut
-
-sub dest_regionname {
- my $self = shift;
- $self->dest_region->regionname;
-}
-
-=item dest_regionname
-
-Returns a short list of the prefixes for the destination region
-(see L<FS::rate_region>) associated with this call plan rate.
-
-=cut
-
-sub dest_prefixes_short {
- my $self = shift;
- $self->dest_region->prefixes_short;
-}
-
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::rate>, L<FS::rate_region>, L<FS::Record>,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/rate_prefix.pm b/FS/FS/rate_prefix.pm
deleted file mode 100644
index 42b004f..0000000
--- a/FS/FS/rate_prefix.pm
+++ /dev/null
@@ -1,139 +0,0 @@
-package FS::rate_prefix;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-use FS::rate_region;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::rate_prefix - Object methods for rate_prefix records
-
-=head1 SYNOPSIS
-
- use FS::rate_prefix;
-
- $record = new FS::rate_prefix \%hash;
- $record = new FS::rate_prefix { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::rate_prefix object represents an call rating prefix. FS::rate_prefix
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item prefixnum - primary key
-
-=item regionnum - call ration region (see L<FS::rate_region>)
-
-=item countrycode
-
-=item npa
-
-=item nxx
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new prefix. To add the prefix to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'rate_prefix'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid prefix. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('prefixnum')
- || $self->ut_foreign_key('regionnum', 'rate_region', 'regionnum' )
- || $self->ut_number('countrycode')
- || $self->ut_numbern('npa')
- || $self->ut_numbern('nxx')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item rate_region
-
-Returns the rate region (see L<FS::rate_region>) for this prefix.
-
-=cut
-
-sub rate_region {
- my $self = shift;
- qsearchs('rate_region', { 'regionnum' => $self->regionnum } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::rate_region>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/rate_region.pm b/FS/FS/rate_region.pm
deleted file mode 100644
index 65dfd2a..0000000
--- a/FS/FS/rate_region.pm
+++ /dev/null
@@ -1,313 +0,0 @@
-package FS::rate_region;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs dbh );
-use FS::rate_prefix;
-use FS::rate_detail;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::rate_region - Object methods for rate_region records
-
-=head1 SYNOPSIS
-
- use FS::rate_region;
-
- $record = new FS::rate_region \%hash;
- $record = new FS::rate_region { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::rate_region object represents an call rating region. FS::rate_region
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item regionnum - primary key
-
-=item regionname
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new region. To add the region to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'rate_region'; }
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-Currently available options are: I<rate_prefix> and I<dest_detail>
-
-If I<rate_prefix> is set to an array reference of FS::rate_prefix objects, the
-objects will have their regionnum field set and will be inserted after this
-record.
-
-If I<dest_detail> is set to an array reference of FS::rate_detail objects, the
-objects will have their dest_regionnum field set and will be inserted after
-this record.
-
-
-=cut
-
-sub insert {
- my $self = shift;
- my %options = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->check;
- return $error if $error;
-
- $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $options{'rate_prefix'} ) {
- foreach my $rate_prefix ( @{$options{'rate_prefix'}} ) {
- $rate_prefix->regionnum($self->regionnum);
- $error = $rate_prefix->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- if ( $options{'dest_detail'} ) {
- foreach my $rate_detail ( @{$options{'dest_detail'}} ) {
- $rate_detail->dest_regionnum($self->regionnum);
- $error = $rate_detail->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD [ , OPTION => VALUE ... ]
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-Currently available options are: I<rate_prefix> and I<dest_detail>
-
-If I<rate_prefix> is set to an array reference of FS::rate_prefix objects, the
-objects will have their regionnum field set and will be inserted after this
-record. Any existing rate_prefix records associated with this record will be
-deleted.
-
-If I<dest_detail> is set to an array reference of FS::rate_detail objects, the
-objects will have their dest_regionnum field set and will be inserted after
-this record. Any existing rate_detail records associated with this record will
-be deleted.
-
-=cut
-
-sub replace {
- my ($new, $old) = (shift, shift);
- my %options = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my @old_rate_prefix = ();
- @old_rate_prefix = $old->rate_prefix if $options{'rate_prefix'};
- my @old_dest_detail = ();
- @old_dest_detail = $old->dest_detail if $options{'dest_detail'};
-
- my $error = $new->SUPER::replace($old);
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- foreach my $old_rate_prefix ( @old_rate_prefix ) {
- my $error = $old_rate_prefix->delete;
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- foreach my $old_dest_detail ( @old_dest_detail ) {
- my $error = $old_dest_detail->delete;
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- foreach my $rate_prefix ( @{$options{'rate_prefix'}} ) {
- $rate_prefix->regionnum($new->regionnum);
- $error = $rate_prefix->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- foreach my $rate_detail ( @{$options{'dest_detail'}} ) {
- $rate_detail->dest_regionnum($new->regionnum);
- $error = $rate_detail->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item check
-
-Checks all fields to make sure this is a valid region. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('regionnum')
- || $self->ut_text('regionname')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item rate_prefix
-
-Returns all prefixes (see L<FS::rate_prefix>) for this region.
-
-=cut
-
-sub rate_prefix {
- my $self = shift;
-
- sort { $a->countrycode cmp $b->countrycode
- or $a->npa cmp $b->npa
- or $a->nxx cmp $b->nxx
- }
- qsearch( 'rate_prefix', { 'regionnum' => $self->regionnum } );
-}
-
-=item dest_detail
-
-Returns all rate details (see L<FS::rate_detail>) for this region as a
-destionation.
-
-=cut
-
-sub dest_detail {
- my $self = shift;
- qsearch( 'rate_detail', { 'dest_regionnum' => $self->regionnum, } );
-}
-
-=item prefixes_short
-
-Returns a string representing all the prefixes for this region.
-
-=cut
-
-sub prefixes_short {
- my $self = shift;
-
- my $countrycode = '';
- my $out = '';
-
- foreach my $rate_prefix ( $self->rate_prefix ) {
- if ( $countrycode ne $rate_prefix->countrycode ) {
- $out =~ s/, $//;
- $countrycode = $rate_prefix->countrycode;
- $out.= " +$countrycode ";
- }
- my $npa = $rate_prefix->npa;
- if ( $countrycode eq '1' ) {
- $out .= '('. substr( $npa, 0, 3 ). ')';
- $out .= ' '. substr( $npa, 3 ) if length($npa) > 3;
- } else {
- $out .= $rate_prefix->npa;
- }
- $out .= ', ';
- }
- $out =~ s/, $//;
-
- $out;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/reason.pm b/FS/FS/reason.pm
deleted file mode 100644
index 5311ec5..0000000
--- a/FS/FS/reason.pm
+++ /dev/null
@@ -1,184 +0,0 @@
-package FS::reason;
-
-use strict;
-use vars qw( @ISA $DEBUG $me );
-use DBIx::DBSchema;
-use DBIx::DBSchema::Table;
-use DBIx::DBSchema::Column;
-use FS::Record qw( qsearch qsearchs dbh dbdef );
-use FS::reason_type;
-
-@ISA = qw(FS::Record);
-$DEBUG = 0;
-$me = '[FS::reason]';
-
-=head1 NAME
-
-FS::reason - Object methods for reason records
-
-=head1 SYNOPSIS
-
- use FS::reason;
-
- $record = new FS::reason \%hash;
- $record = new FS::reason { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::reason object represents a reason message. FS::reason inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item reasonnum - primary key
-
-=item reason_type - index into FS::reason_type
-
-=item reason - text of the reason
-
-=item disabled - 'Y' or ''
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new reason. To add the example to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'reason'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-=item check
-
-Checks all fields to make sure this is a valid reason. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('reasonnum')
- || $self->ut_text('reason')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item reasontype
-
-Returns the reason_type (see <I>FS::reason_type</I>) associated with this reason.
-
-=cut
-
-sub reasontype {
- qsearchs( 'reason_type', { 'typenum' => shift->reason_type } );
-}
-
-# _upgrade_data
-#
-# Used by FS::Upgrade to migrate to a new database.
-#
-#
-
-sub _upgrade_data { # class method
- my ($self, %opts) = @_;
- my $dbh = dbh;
-
- warn "$me upgrading $self\n" if $DEBUG;
-
- my $column = dbdef->table($self->table)->column('reason');
- unless ($column->type eq 'text') { # assume history matches main table
-
- # ideally this would be supported in DBIx-DBSchema and friends
- warn "$me Shifting reason column to type 'text'\n" if $DEBUG;
- foreach my $table ( $self->table, 'h_'. $self->table ) {
- my @sql = ();
-
- $column = dbdef->table($self->table)->column('reason');
- my $columndef = $column->line($dbh);
- $columndef =~ s/varchar\(\d+\)/text/i;
-
- if ( $dbh->{Driver}->{Name} eq 'Pg' ) {
-
- my $notnull = $columndef =~ s/not null//i;
- push @sql,"ALTER TABLE $table RENAME reason TO freeside_upgrade_reason";
- push @sql,"ALTER TABLE $table ADD $columndef";
- push @sql,"UPDATE $table SET reason = freeside_upgrade_reason";
- push @sql,"ALTER TABLE $table ALTER reason SET NOT NULL"
- if $notnull;
- push @sql,"ALTER TABLE $table DROP freeside_upgrade_reason";
-
- } elsif ( $dbh->{Driver}->{Name} =~ /^mysql/i ){
-
- #crap, this isn't working
- #push @sql,"ALTER TABLE $table MODIFY reason ". $column->line($dbh);
- warn "WARNING: reason table upgrade not yet supported for mysql, sorry";
-
- } else {
- die "watchu talkin' 'bout, Willis? (unsupported database type)";
- }
-
- foreach (@sql) {
- my $sth = $dbh->prepare($_) or die $dbh->errstr;
- $sth->execute or die $sth->errstr;
- }
- }
- }
-
- '';
-
-}
-=back
-
-=head1 BUGS
-
-Here be termintes. Don't use on wooden computers.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/reason_type.pm b/FS/FS/reason_type.pm
deleted file mode 100644
index 482ea34..0000000
--- a/FS/FS/reason_type.pm
+++ /dev/null
@@ -1,211 +0,0 @@
-package FS::reason_type;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-our %class_name = (
- 'C' => 'cancel',
- 'R' => 'credit',
- 'S' => 'suspend',
-);
-
-our %class_purpose = (
- 'C' => 'explain why a customer package was cancelled',
- 'R' => 'explain why a customer was credited',
- 'S' => 'explain why a customer package was suspended',
-);
-
-=head1 NAME
-
-FS::reason_type - Object methods for reason_type records
-
-=head1 SYNOPSIS
-
- use FS::reason_type;
-
- $record = new FS::reason_type \%hash;
- $record = new FS::reason_type { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::reason_type object represents a grouping of reasons. FS::reason_type
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item typenum - primary key
-
-=item class - currently 'C', 'R', or 'S' for cancel, credit, or suspend
-
-=item type - name of the type of reason
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new reason_type. To add the example to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table { 'reason_type'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-=item check
-
-Checks all fields to make sure this is a valid reason_type. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('typenum')
- || $self->ut_enum('class', [ keys %class_name ] )
- || $self->ut_text('type')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item reasons
-
-Returns a list of all reasons associated with this type.
-
-=cut
-
-sub reasons {
- qsearch( 'reason', { 'reason_type' => shift->typenum } );
-}
-
-=item enabled_reasons
-
-Returns a list of enabled reasons associated with this type.
-
-=cut
-
-sub enabled_reasons {
- qsearch( 'reason', { 'reason_type' => shift->typenum,
- 'enabled' => '',
- } );
-}
-
-# _populate_initial_data
-#
-# Used by FS::Setup to initialize a new database.
-#
-#
-
-sub _populate_initial_data { # class method
- my ($self, %opts) = @_;
-
- my $conf = new FS::Conf;
-
- foreach ( keys %class_name ) {
- my $object = $self->new( {'class' => $_,
- 'type' => ucfirst($class_name{$_}). ' Reason',
- } );
- my $error = $object->insert();
- die "error inserting $self into database: $error\n"
- if $error;
- }
-
- my $object = qsearchs('reason_type', { 'class' => 'R' });
- die "can't find credit reason type just inserted!\n"
- unless $object;
-
- foreach ( keys %FS::cust_credit::reasontype_map ) {
-# my $object = $self->new( {'class' => 'R',
-# 'type' => $FS::cust_credit::reasontype_map{$_},
-# } );
-# my $error = $object->insert();
-# die "error inserting $self into database: $error\n"
-# if $error;
-# # or clause for 1.7.x
- $conf->set($_, $object->typenum)
- or die "failed setting config";
- }
-
- '';
-
-}
-
-# _upgrade_data
-#
-# Used by FS::Upgrade to migrate to a new database.
-#
-#
-
-sub _upgrade_data { # class method
- my ($self, %opts) = @_;
-
- foreach ( keys %class_name ) {
- unless (scalar(qsearch('reason_type', { 'class' => $_ }))) {
- my $object = $self->new( {'class' => $_,
- 'type' => ucfirst($class_name{$_}),
- } );
- my $error = $object->insert();
- die "error inserting $self into database: $error\n"
- if $error;
- }
- }
-
- '';
-
-}
-
-=back
-
-=head1 BUGS
-
-Here be termintes. Don't use on wooden computers.
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/reg_code.pm b/FS/FS/reg_code.pm
deleted file mode 100644
index f48ccf0..0000000
--- a/FS/FS/reg_code.pm
+++ /dev/null
@@ -1,223 +0,0 @@
-package FS::reg_code;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw(qsearch dbh);
-use FS::agent;
-use FS::reg_code_pkg;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::reg_code - One-time registration codes
-
-=head1 SYNOPSIS
-
- use FS::reg_code;
-
- $record = new FS::reg_code \%hash;
- $record = new FS::reg_code { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::reg_code object is a one-time registration code. FS::reg_code inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item codenum - primary key
-
-=item code - registration code string
-
-=item agentnum - Agent (see L<FS::agent>)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new registration code. To add the code to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'reg_code'; }
-
-=item insert [ PKGPART_ARRAYREF ]
-
-Adds this record to the database. If an arrayref of pkgparts
-(see L<FS::part_pkg>) is specified, the appropriate reg_code_pkg records
-(see L<FS::reg_code_pkg>) will be inserted.
-
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub insert {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( @_ ) {
- my $pkgparts = shift;
- foreach my $pkgpart ( @$pkgparts ) {
- my $reg_code_pkg = new FS::reg_code_pkg ( {
- 'codenum' => $self->codenum,
- 'pkgpart' => $pkgpart,
- } );
- $error = $reg_code_pkg->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item delete
-
-Delete this record (and all associated reg_code_pkg records) from the database.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $reg_code_pkg ( $self->reg_code_pkg ) {
- my $error = $reg_code_pkg->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid registration code. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('codenum')
- || $self->ut_alpha('code')
- || $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item part_pkg
-
-Returns all package definitions (see L<FS::part_pkg> for this registration
-code.
-
-=cut
-
-sub part_pkg {
- my $self = shift;
- map { $_->part_pkg } $self->reg_code_pkg;
-}
-
-=item reg_code_pkg
-
-Returns all FS::reg_code_pkg records for this registration code.
-
-=cut
-
-sub reg_code_pkg {
- my $self = shift;
- qsearch('reg_code_pkg', { 'codenum' => $self->codenum } );
-}
-
-
-=back
-
-=head1 BUGS
-
-Feeping creaturitis.
-
-=head1 SEE ALSO
-
-L<FS::reg_code_pkg>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
-
diff --git a/FS/FS/reg_code_pkg.pm b/FS/FS/reg_code_pkg.pm
deleted file mode 100644
index 837b755..0000000
--- a/FS/FS/reg_code_pkg.pm
+++ /dev/null
@@ -1,139 +0,0 @@
-package FS::reg_code_pkg;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw(qsearchs);
-use FS::reg_code;
-use FS::part_pkg;
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::reg_code_pkg - Class linking registration codes (see L<FS::reg_code>) with package definitions (see L<FS::part_pkg>)
-
-=head1 SYNOPSIS
-
- use FS::reg_code_pkg;
-
- $record = new FS::reg_code_pkg \%hash;
- $record = new FS::reg_code_pkg { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::reg_code_pkg object links a registration code to a package definition.
-FS::table_name inherits from FS::Record. The following fields are currently
-supported:
-
-=over 4
-
-=item codepkgnum - primary key
-
-=item codenum - registration code (see L<FS::reg_code>)
-
-=item pkgpart - package definition (see L<FS::part_pkg>)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new registration code. To add the registration code to the database,
-see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'reg_code_pkg'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('codepkgnum')
- || $self->ut_foreign_key('codenum', 'reg_code', 'codenum')
- || $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item part_pkg
-
-Returns the package definition (see L<FS::part_pkg>)
-
-=cut
-
-sub part_pkg {
- my $self = shift;
- qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart } );
-}
-
-=back
-
-=head1 BUGS
-
-Feeping creaturitis.
-
-=head1 SEE ALSO
-
-L<FS::reg_code_pkg>, L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
-
diff --git a/FS/FS/registrar.pm b/FS/FS/registrar.pm
deleted file mode 100644
index cf5dc49..0000000
--- a/FS/FS/registrar.pm
+++ /dev/null
@@ -1,119 +0,0 @@
-package FS::registrar;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::registrar - Object methods for registrar records
-
-=head1 SYNOPSIS
-
- use FS::registrar;
-
- $record = new FS::registrar \%hash;
- $record = new FS::registrar { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::registrar object represents a registrar. FS::registrar inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item registrarnum - primary key
-
-=item registrarname -
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new registrar. To add the registrar to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'registrar'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item check
-
-Checks all fields to make sure this is a valid registrar. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('registrarnum')
- || $self->ut_text('registrarname')
- ;
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/router.pm b/FS/FS/router.pm
deleted file mode 100755
index 88ba990..0000000
--- a/FS/FS/router.pm
+++ /dev/null
@@ -1,140 +0,0 @@
-package FS::router;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearchs qsearch );
-use FS::addr_block;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::router - Object methods for router records
-
-=head1 SYNOPSIS
-
- use FS::router;
-
- $record = new FS::router \%hash;
- $record = new FS::router { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::router record describes a broadband router, such as a DSLAM or a wireless
- access point. FS::router inherits from FS::Record. The following
-fields are currently supported:
-
-=over 4
-
-=item routernum - primary key
-
-=item routername - descriptive name for the router
-
-=item svcnum - svcnum of the owning FS::svc_broadband, if appropriate
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Create a new record. To add the record to the database, see "insert".
-
-=cut
-
-sub table { 'router'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this record from the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('routernum')
- || $self->ut_text('routername');
- return $error if $error;
-
- $self->SUPER::check;
-}
-
-=item addr_block
-
-Returns a list of FS::addr_block objects (address blocks) associated
-with this object.
-
-=cut
-
-sub addr_block {
- my $self = shift;
- return qsearch('addr_block', { routernum => $self->routernum });
-}
-
-=item part_svc_router
-
-Returns a list of FS::part_svc_router objects associated with this
-object. This is unlikely to be useful for any purpose other than retrieving
-the associated FS::part_svc objects. See below.
-
-=cut
-
-sub part_svc_router {
- my $self = shift;
- return qsearch('part_svc_router', { routernum => $self->routernum });
-}
-
-=item part_svc
-
-Returns a list of FS::part_svc objects associated with this object.
-
-=cut
-
-sub part_svc {
- my $self = shift;
- return map { qsearchs('part_svc', { svcpart => $_->svcpart }) }
- $self->part_svc_router;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-FS::svc_broadband, FS::router, FS::addr_block, FS::part_svc,
-schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/session.pm b/FS/FS/session.pm
deleted file mode 100644
index 615c8ae..0000000
--- a/FS/FS/session.pm
+++ /dev/null
@@ -1,265 +0,0 @@
-package FS::session;
-
-use strict;
-use vars qw( @ISA $conf $start $stop );
-use FS::UID qw( dbh );
-use FS::Record qw( qsearchs );
-use FS::svc_acct;
-use FS::port;
-use FS::nas;
-
-@ISA = qw(FS::Record);
-
-$FS::UID::callback{'FS::session'} = sub {
- $conf = new FS::Conf;
- $start = $conf->exists('session-start') ? $conf->config('session-start') : '';
- $stop = $conf->exists('session-stop') ? $conf->config('session-stop') : '';
-};
-
-=head1 NAME
-
-FS::session - Object methods for session records
-
-=head1 SYNOPSIS
-
- use FS::session;
-
- $record = new FS::session \%hash;
- $record = new FS::session {
- 'portnum' => 1,
- 'svcnum' => 2,
- 'login' => $timestamp,
- 'logout' => $timestamp,
- };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->nas_heartbeat($timestamp);
-
-=head1 DESCRIPTION
-
-An FS::session object represents an user login session. FS::session inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item sessionnum - primary key
-
-=item portnum - NAS port for this session - see L<FS::port>
-
-=item svcnum - User for this session - see L<FS::svc_acct>
-
-=item login - timestamp indicating the beginning of this user session.
-
-=item logout - timestamp indicating the end of this user session. May be null,
- which indicates a currently open session.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new session. To add the session to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'session'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false. If the `login' field is empty, it is replaced with
-the current time.
-
-=cut
-
-sub insert {
- my $self = shift;
- my $error;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- $error = $self->check;
- return $error if $error;
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- if ( qsearchs('session', { 'portnum' => $self->portnum, 'logout' => '' } ) ) {
- $dbh->rollback if $oldAutoCommit;
- return "a session on that port is already open!";
- }
-
- $self->setfield('login', time()) unless $self->getfield('login');
-
- $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $self->nas_heartbeat($self->getfield('login'));
-
- #session-starting callback
- #redundant with heartbeat, yuck
- my $port = qsearchs('port',{'portnum'=>$self->portnum});
- my $nas = qsearchs('nas',{'nasnum'=>$port->nasnum});
- #kcuy
- my( $ip, $nasip, $nasfqdn ) = ( $port->ip, $nas->nasip, $nas->nasfqdn );
- system( eval qq("$start") ) if $start;
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false. If the `logout' field is empty,
-it is replaced with the current time.
-
-=cut
-
-sub replace {
- my($self, $old) = @_;
- my $error;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $error = $self->check;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $self->setfield('logout', time()) unless $self->getfield('logout');
-
- $error = $self->SUPER::replace($old);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $self->nas_heartbeat($self->getfield('logout'));
-
- #session-ending callback
- #redundant with heartbeat, yuck
- my $port = qsearchs('port',{'portnum'=>$self->portnum});
- my $nas = qsearchs('nas',{'nasnum'=>$port->nasnum});
- #kcuy
- my( $ip, $nasip, $nasfqdn ) = ( $port->ip, $nas->nasip, $nas->nasfqdn );
- system( eval qq("$stop") ) if $stop;
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-=item check
-
-Checks all fields to make sure this is a valid session. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
- my $error =
- $self->ut_numbern('sessionnum')
- || $self->ut_number('portnum')
- || $self->ut_number('svcnum')
- || $self->ut_numbern('login')
- || $self->ut_numbern('logout')
- ;
- return $error if $error;
- return "Unknown svcnum"
- unless qsearchs('svc_acct', { 'svcnum' => $self->svcnum } );
- $self->SUPER::check;
-}
-
-=item nas_heartbeat
-
-Heartbeats the nas associated with this session (see L<FS::nas>).
-
-=cut
-
-sub nas_heartbeat {
- my $self = shift;
- my $port = qsearchs('port',{'portnum'=>$self->portnum});
- my $nas = qsearchs('nas',{'nasnum'=>$port->nasnum});
- $nas->heartbeat(shift);
-}
-
-=item svc_acct
-
-Returns the svc_acct record associated with this session (see L<FS::svc_acct>).
-
-=cut
-
-sub svc_acct {
- my $self = shift;
- qsearchs('svc_acct', { 'svcnum' => $self->svcnum } );
-}
-
-=back
-
-=head1 BUGS
-
-Maybe you shouldn't be able to insert a session if there's currently an open
-session on that port. Or maybe the open session on that port should be flagged
-as problematic? autoclosed? *sigh*
-
-Hmm, sessions refer to current svc_acct records... probably need to constrain
-deletions to svc_acct records such that no svc_acct records are deleted which
-have a session (even if long-closed).
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm
deleted file mode 100644
index d830f2f..0000000
--- a/FS/FS/svc_Common.pm
+++ /dev/null
@@ -1,828 +0,0 @@
-package FS::svc_Common;
-
-use strict;
-use vars qw( @ISA $noexport_hack $DEBUG $me );
-use Carp qw( cluck carp croak ); #specify cluck have to specify them all..
-use FS::Record qw( qsearch qsearchs fields dbh );
-use FS::cust_main_Mixin;
-use FS::cust_svc;
-use FS::part_svc;
-use FS::queue;
-use FS::cust_main;
-use FS::inventory_item;
-use FS::inventory_class;
-
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
-$me = '[FS::svc_Common]';
-$DEBUG = 0;
-
-=head1 NAME
-
-FS::svc_Common - Object method for all svc_ records
-
-=head1 SYNOPSIS
-
-use FS::svc_Common;
-
-@ISA = qw( FS::svc_Common );
-
-=head1 DESCRIPTION
-
-FS::svc_Common is intended as a base class for table-specific classes to
-inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record.
-
-=head1 METHODS
-
-=over 4
-
-=item search_sql_field FIELD STRING
-
-Class method which returns an SQL fragment to search for STRING in FIELD.
-
-=cut
-
-sub search_sql_field {
- my( $class, $field, $string ) = @_;
- my $table = $class->table;
- my $q_string = dbh->quote($string);
- "$table.$field = $q_string";
-}
-
-#fallback for services that don't provide a search...
-sub search_sql {
- #my( $class, $string ) = @_;
- '1 = 0'; #false
-}
-
-=item new
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
- bless ($self, $class);
-
- unless ( defined ( $self->table ) ) {
- $self->{'Table'} = shift;
- carp "warning: FS::Record::new called with table name ". $self->{'Table'};
- }
-
- #$self->{'Hash'} = shift;
- my $newhash = shift;
- $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) };
-
- $self->setdefault( $self->_fieldhandlers )
- unless $self->svcnum;
-
- $self->{'Hash'}{$_} = $newhash->{$_}
- foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) }
- keys %$newhash;
-
- foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) {
- $self->{'Hash'}{$field}='';
- }
-
- $self->_rebless if $self->can('_rebless');
-
- $self->{'modified'} = 0;
-
- $self->_cache($self->{'Hash'}, shift) if $self->can('_cache') && @_;
-
- $self;
-}
-
-#empty default
-sub _fieldhandlers { {}; }
-
-sub virtual_fields {
-
- # This restricts the fields based on part_svc_column and the svcpart of
- # the service. There are four possible cases:
- # 1. svcpart passed as part of the svc_x hash.
- # 2. svcpart fetched via cust_svc based on svcnum.
- # 3. No svcnum or svcpart. In this case, return ALL the fields with
- # dbtable eq $self->table.
- # 4. Called via "fields('svc_acct')" or something similar. In this case
- # there is no $self object.
-
- my $self = shift;
- my $svcpart;
- my @vfields = $self->SUPER::virtual_fields;
-
- return @vfields unless (ref $self); # Case 4
-
- if ($self->svcpart) { # Case 1
- $svcpart = $self->svcpart;
- } elsif ( $self->svcnum
- && qsearchs('cust_svc',{'svcnum'=>$self->svcnum} )
- ) { #Case 2
- $svcpart = $self->cust_svc->svcpart;
- } else { # Case 3
- $svcpart = '';
- }
-
- if ($svcpart) { #Cases 1 and 2
- my %flags = map { $_->columnname, $_->columnflag } (
- qsearch ('part_svc_column', { svcpart => $svcpart } )
- );
- return grep { not ( defined($flags{$_}) && $flags{$_} eq 'X') } @vfields;
- } else { # Case 3
- return @vfields;
- }
- return ();
-}
-
-=item label
-
-svc_Common provides a fallback label subroutine that just returns the svcnum.
-
-=cut
-
-sub label {
- my $self = shift;
- cluck "warning: ". ref($self). " not loaded or missing label method; ".
- "using svcnum";
- $self->svcnum;
-}
-
-=item check
-
-Checks the validity of fields in this record.
-
-At present, this does nothing but call FS::Record::check (which, in turn,
-does nothing but run virtual field checks).
-
-=cut
-
-sub check {
- my $self = shift;
- $self->SUPER::check;
-}
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
-defined. An FS::cust_svc record will be created and inserted.
-
-Currently available options are: I<jobnums>, I<child_objects> and
-I<depend_jobnum>.
-
-If I<jobnum> is set to an array reference, the jobnums of any export jobs will
-be added to the referenced array.
-
-If I<child_objects> is set to an array reference of FS::tablename objects (for
-example, FS::acct_snarf objects), they will have their svcnum field set and
-will be inserted after this record, but before any exports are run. Each
-element of the array can also optionally be a two-element array reference
-containing the child object and the name of an alternate field to be filled in
-with the newly-inserted svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
-
-If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
-jobnums), all provisioning jobs will have a dependancy on the supplied
-jobnum(s) (they will not run until the specific job(s) complete(s)).
-
-If I<export_args> is set to an array reference, the referenced list will be
-passed to export commands.
-
-=cut
-
-sub insert {
- my $self = shift;
- my %options = @_;
- warn "[$me] insert called with options ".
- join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
- if $DEBUG;
-
- my @jobnums = ();
- local $FS::queue::jobnums = \@jobnums;
- warn "[$me] insert: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
- if $DEBUG;
- my $objects = $options{'child_objects'} || [];
- my $depend_jobnums = $options{'depend_jobnum'} || [];
- $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
- my $error;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $error = $self->check;
- return $error if $error;
-
- my $svcnum = $self->svcnum;
- my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : '';
- #unless ( $svcnum ) {
- if ( !$svcnum or !$cust_svc ) {
- $cust_svc = new FS::cust_svc ( {
- #hua?# 'svcnum' => $svcnum,
- 'svcnum' => $self->svcnum,
- 'pkgnum' => $self->pkgnum,
- 'svcpart' => $self->svcpart,
- } );
- $error = $cust_svc->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $svcnum = $self->svcnum($cust_svc->svcnum);
- } else {
- #$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
- unless ( $cust_svc ) {
- $dbh->rollback if $oldAutoCommit;
- return "no cust_svc record found for svcnum ". $self->svcnum;
- }
- $self->pkgnum($cust_svc->pkgnum);
- $self->svcpart($cust_svc->svcpart);
- }
-
- $error = $self->set_auto_inventory;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $error = $self->SUPER::insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- foreach my $object ( @$objects ) {
- my($field, $obj);
- if ( ref($object) eq 'ARRAY' ) {
- ($obj, $field) = @$object;
- } else {
- $obj = $object;
- $field = 'svcnum';
- }
- $obj->$field($self->svcnum);
- $error = $obj->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- #new-style exports!
- unless ( $noexport_hack ) {
-
- warn "[$me] insert: \$FS::queue::jobnums is $FS::queue::jobnums\n"
- if $DEBUG;
-
- my $export_args = $options{'export_args'} || [];
-
- foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
- my $error = $part_export->export_insert($self, @$export_args);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "exporting to ". $part_export->exporttype.
- " (transaction rolled back): $error";
- }
- }
-
- foreach my $depend_jobnum ( @$depend_jobnums ) {
- warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
- if $DEBUG;
- foreach my $jobnum ( @jobnums ) {
- my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
- warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
- if $DEBUG;
- my $error = $queue->depend_insert($depend_jobnum);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error queuing job dependancy: $error";
- }
- }
- }
-
- }
-
- if ( exists $options{'jobnums'} ) {
- push @{ $options{'jobnums'} }, @jobnums;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-=item delete [ , OPTION => VALUE ... ]
-
-Deletes this account from the database. If there is an error, returns the
-error, otherwise returns false.
-
-The corresponding FS::cust_svc record will be deleted as well.
-
-=cut
-
-sub delete {
- my $self = shift;
- my %options = @_;
- my $export_args = $options{'export_args'} || [];
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::delete
- || $self->export('delete', @$export_args)
- || $self->return_inventory
- || $self->cust_svc->delete
- ;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-sub replace {
- my ($new, $old) = (shift, shift);
- my %options = @_;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- # We absolutely have to have an old vs. new record to make this work.
- $old = $new->replace_old unless defined($old);
-
- my $error = $new->set_auto_inventory;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $error = $new->SUPER::replace($old);
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- #new-style exports!
- unless ( $noexport_hack ) {
-
- my $export_args = $options{'export_args'} || [];
-
- #not quite false laziness, but same pattern as FS::svc_acct::replace and
- #FS::part_export::sqlradius::_export_replace. List::Compare or something
- #would be useful but too much of a pain in the ass to deploy
-
- my @old_part_export = $old->cust_svc->part_svc->part_export;
- my %old_exportnum = map { $_->exportnum => 1 } @old_part_export;
- my @new_part_export =
- $new->svcpart
- ? qsearchs('part_svc', { svcpart=>$new->svcpart } )->part_export
- : $new->cust_svc->part_svc->part_export;
- my %new_exportnum = map { $_->exportnum => 1 } @new_part_export;
-
- foreach my $delete_part_export (
- grep { ! $new_exportnum{$_->exportnum} } @old_part_export
- ) {
- my $error = $delete_part_export->export_delete($old, @$export_args);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error deleting, export to ". $delete_part_export->exporttype.
- " (transaction rolled back): $error";
- }
- }
-
- foreach my $replace_part_export (
- grep { $old_exportnum{$_->exportnum} } @new_part_export
- ) {
- my $error =
- $replace_part_export->export_replace( $new, $old, @$export_args);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error exporting to ". $replace_part_export->exporttype.
- " (transaction rolled back): $error";
- }
- }
-
- foreach my $insert_part_export (
- grep { ! $old_exportnum{$_->exportnum} } @new_part_export
- ) {
- my $error = $insert_part_export->export_insert($new, @$export_args );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error inserting export to ". $insert_part_export->exporttype.
- " (transaction rolled back): $error";
- }
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-=item setfixed
-
-Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
-error, returns the error, otherwise returns the FS::part_svc object (use ref()
-to test the return). Usually called by the check method.
-
-=cut
-
-sub setfixed {
- my $self = shift;
- $self->setx('F', @_);
-}
-
-=item setdefault
-
-Sets all fields to their defaults (see L<FS::part_svc>), overriding their
-current values. If there is an error, returns the error, otherwise returns
-the FS::part_svc object (use ref() to test the return).
-
-=cut
-
-sub setdefault {
- my $self = shift;
- $self->setx('D', @_ );
-}
-
-=item set_default_and_fixed
-
-=cut
-
-sub set_default_and_fixed {
- my $self = shift;
- $self->setx( [ 'D', 'F' ], @_ );
-}
-
-=item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ]
-
-Sets fields according to the passed in flag or arrayref of flags.
-
-Optionally, a hashref of field names and callback coderefs can be passed.
-If a coderef exists for a given field name, instead of setting the field,
-the coderef is called with the column value (part_svc_column.columnvalue)
-as the single parameter.
-
-=cut
-
-sub setx {
- my $self = shift;
- my $x = shift;
- my @x = ref($x) ? @$x : ($x);
- my $coderef = scalar(@_) ? shift : $self->_fieldhandlers;
-
- my $error =
- $self->ut_numbern('svcnum')
- ;
- return $error if $error;
-
- my $part_svc = $self->part_svc;
- return "Unkonwn svcpart" unless $part_svc;
-
- #set default/fixed/whatever fields from part_svc
-
- foreach my $part_svc_column (
- grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x
- $part_svc->all_part_svc_column
- ) {
-
- my $columnname = $part_svc_column->columnname;
- my $columnvalue = $part_svc_column->columnvalue;
-
- $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue )
- if exists( $coderef->{$columnname} );
- $self->setfield( $columnname, $columnvalue );
-
- }
-
- $part_svc;
-
-}
-
-sub part_svc {
- my $self = shift;
-
- #get part_svc
- my $svcpart;
- if ( $self->get('svcpart') ) {
- $svcpart = $self->get('svcpart');
- } elsif ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) {
- my $cust_svc = $self->cust_svc;
- return "Unknown svcnum" unless $cust_svc;
- $svcpart = $cust_svc->svcpart;
- }
-
- qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
-
-}
-
-=item set_auto_inventory
-
-Sets any fields which auto-populate from inventory (see L<FS::part_svc>).
-If there is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub set_auto_inventory {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('svcnum')
- ;
- return $error if $error;
-
- my $part_svc = $self->part_svc;
- return "Unkonwn svcpart" unless $part_svc;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- #set default/fixed/whatever fields from part_svc
- my $table = $self->table;
- foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
- my $part_svc_column = $part_svc->part_svc_column($field);
- if ( $part_svc_column->columnflag eq 'A' && $self->$field() eq '' ) {
-
- my $classnum = $part_svc_column->columnvalue;
- my $inventory_item = qsearchs({
- 'table' => 'inventory_item',
- 'hashref' => { 'classnum' => $classnum,
- 'svcnum' => '',
- },
- 'extra_sql' => 'LIMIT 1 FOR UPDATE',
- });
-
- unless ( $inventory_item ) {
- $dbh->rollback if $oldAutoCommit;
- my $inventory_class =
- qsearchs('inventory_class', { 'classnum' => $classnum } );
- return "Can't find inventory_class.classnum $classnum"
- unless $inventory_class;
- return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
- #for pluralizing
- }
-
- $inventory_item->svcnum( $self->svcnum );
- my $ierror = $inventory_item->replace();
- if ( $ierror ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error provisioning inventory: $ierror";
-
- }
-
- $self->setfield( $field, $inventory_item->item );
-
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-
-}
-
-=item return_inventory
-
-=cut
-
-sub return_inventory {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $inventory_item ( $self->inventory_item ) {
- $inventory_item->svcnum('');
- my $error = $inventory_item->replace();
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error returning inventory: $error";
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- '';
-}
-
-=item inventory_item
-
-Returns the inventory items associated with this svc_ record, as
-FS::inventory_item objects (see L<FS::inventory_item>.
-
-=cut
-
-sub inventory_item {
- my $self = shift;
- qsearch({
- 'table' => 'inventory_item',
- 'hashref' => { 'svcnum' => $self->svcnum, },
- });
-}
-
-=item cust_svc
-
-Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
-object (see L<FS::cust_svc>).
-
-=cut
-
-sub cust_svc {
- my $self = shift;
- qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
-}
-
-=item suspend
-
-Runs export_suspend callbacks.
-
-=cut
-
-sub suspend {
- my $self = shift;
- my %options = @_;
- my $export_args = $options{'export_args'} || [];
- $self->export('suspend', @$export_args);
-}
-
-=item unsuspend
-
-Runs export_unsuspend callbacks.
-
-=cut
-
-sub unsuspend {
- my $self = shift;
- my %options = @_;
- my $export_args = $options{'export_args'} || [];
- $self->export('unsuspend', @$export_args);
-}
-
-=item export_links
-
-Runs export_links callbacks and returns the links.
-
-=cut
-
-sub export_links {
- my $self = shift;
- my $return = [];
- $self->export('links', $return);
- $return;
-}
-
-=item export HOOK [ EXPORT_ARGS ]
-
-Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
-
-=cut
-
-sub export {
- my( $self, $method ) = ( shift, shift );
-
- $method = "export_$method" unless $method =~ /^export_/;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- #new-style exports!
- unless ( $noexport_hack ) {
- foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
- next unless $part_export->can($method);
- my $error = $part_export->$method($self, @_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error exporting $method event to ". $part_export->exporttype.
- " (transaction rolled back): $error";
- }
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-=item overlimit
-
-Sets or retrieves overlimit date.
-
-=cut
-
-sub overlimit {
- my $self = shift;
- $self->cust_svc->overlimit(@_);
-}
-
-=item cancel
-
-Stub - returns false (no error) so derived classes don't need to define this
-methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-This method is called *before* the deletion step which actually deletes the
-services. This method should therefore only be used for "pre-deletion"
-cancellation steps, if necessary.
-
-=cut
-
-sub cancel { ''; }
-
-=item clone_suspended
-
-Constructor used by FS::part_export::_export_suspend fallback. Stub returning
-same object for svc_ classes which don't implement a suspension fallback
-(everything except svc_acct at the moment). Document better.
-
-=cut
-
-sub clone_suspended {
- shift;
-}
-
-=item clone_kludge_unsuspend
-
-Constructor used by FS::part_export::_export_unsuspend fallback. Stub returning
-same object for svc_ classes which don't implement a suspension fallback
-(everything except svc_acct at the moment). Document better.
-
-=cut
-
-sub clone_kludge_unsuspend {
- shift;
-}
-
-=back
-
-=head1 BUGS
-
-The setfixed method return value.
-
-B<export> method isn't used by insert and replace methods yet.
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
-from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/svc_External_Common.pm b/FS/FS/svc_External_Common.pm
deleted file mode 100644
index a5805aa..0000000
--- a/FS/FS/svc_External_Common.pm
+++ /dev/null
@@ -1,199 +0,0 @@
-package FS::svc_External_Common;
-
-use strict;
-use vars qw(@ISA);
-use FS::svc_Common;
-
-@ISA = qw( FS::svc_Common );
-
-=head1 NAME
-
-FS::svc_external - Object methods for svc_external records
-
-=head1 SYNOPSIS
-
- use FS::svc_external;
-
- $record = new FS::svc_external \%hash;
- $record = new FS::svc_external { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $error = $record->cancel;
-
-=head1 DESCRIPTION
-
-FS::svc_External_Common is intended as a base class for table-specific classes
-to inherit from. FS::svc_External_Common is used for services which connect
-to externally tracked services via "id" and "table" fields.
-
-FS::svc_External_Common inherits from FS::svc_Common.
-
-The following fields are currently supported:
-
-=over 4
-
-=item svcnum - primary key
-
-=item id - unique number of external record
-
-=item title - for invoice line items
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item search_sql
-
-Provides a default search_sql method which returns an SQL fragment to search
-the B<title> field.
-
-=cut
-
-sub search_sql {
- my($class, $string) = @_;
- $class->search_sql_field('title', $string);
-}
-
-=item new HASHREF
-
-Creates a new external service. To add the external service to the database,
-see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-=item label
-
-Returns a string identifying this external service in the form "id:title"
-
-=cut
-
-sub label {
- my $self = shift;
- $self->id. ':'. $self->title;
-}
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this external service to the database. If there is an error, returns the
-error, otherwise returns false.
-
-The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
-defined. An FS::cust_svc record will be created and inserted.
-
-Currently available options are: I<depend_jobnum>
-
-If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
-jobnums), all provisioning jobs will have a dependancy on the supplied
-jobnum(s) (they will not run until the specific job(s) complete(s)).
-
-=cut
-
-#sub insert {
-# my $self = shift;
-# my $error;
-#
-# $error = $self->SUPER::insert(@_);
-# return $error if $error;
-#
-# '';
-#}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-#sub delete {
-# my $self = shift;
-# my $error;
-#
-# $error = $self->SUPER::delete;
-# return $error if $error;
-#
-# '';
-#}
-
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-#sub replace {
-# my ( $new, $old ) = ( shift, shift );
-# my $error;
-#
-# $error = $new->SUPER::replace($old);
-# return $error if $error;
-#
-# '';
-#}
-
-=item suspend
-
-Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item unsuspend
-
-Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item cancel
-
-Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item check
-
-Checks all fields to make sure this is a valid external service. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $x = $self->setfixed;
- return $x unless ref($x);
- my $part_svc = $x;
-
- my $error =
- $self->ut_numbern('svcnum')
- || $self->ut_numbern('id')
- || $self->ut_textn('title')
- ;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
-L<FS::cust_pkg>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/svc_Parent_Mixin.pm b/FS/FS/svc_Parent_Mixin.pm
deleted file mode 100644
index 4501baf..0000000
--- a/FS/FS/svc_Parent_Mixin.pm
+++ /dev/null
@@ -1,103 +0,0 @@
-package FS::svc_Parent_Mixin;
-
-use strict;
-use NEXT;
-use FS::Record qw(qsearch qsearchs);
-use FS::cust_svc;
-
-=head1 NAME
-
-FS::svc_Parent_Mixin - Mixin class for svc_ classes with a parent_svcnum field
-
-=head1 SYNOPSIS
-
-package FS::svc_table;
-use vars qw(@ISA);
-@ISA = qw( FS::svc_Parent_Mixin FS::svc_Common );
-
-=head1 DESCRIPTION
-
-This is a mixin class for svc_ classes that contain a parent_svcnum field.
-
-=cut
-
-=head1 METHODS
-
-=over 4
-
-=item parent_cust_svc
-
-Returns the parent FS::cust_svc object.
-
-=cut
-
-sub parent_cust_svc {
- my $self = shift;
- qsearchs('cust_svc', { 'svcnum' => $self->parent_svcnum } );
-}
-
-=item parent_svc_x
-
-Returns the corresponding parent FS::svc_ object.
-
-=cut
-
-sub parent_svc_x {
- my $self = shift;
- $self->parent_cust_svc->svc_x;
-}
-
-=item children_cust_svc
-
-Returns a list of any child FS::cust_svc objects.
-
-Note: This is not recursive; it only returns direct children.
-
-=cut
-
-sub children_cust_svc {
- my $self = shift;
- qsearch('cust_svc', { 'parent_svcnum' => $self->svcnum } );
-}
-
-=item children_svc_x
-
-Returns the corresponding list of child FS::svc_ objects.
-
-=cut
-
-sub children_svc_x {
- my $self = shift;
- map { $_->svc_x } $self->children_cust_svc;
-}
-
-=item check
-
-This class provides a check subroutine which takes care of checking the
-parent_svcnum field. The svc_ class which uses it will call SUPER::check at
-the end of its own checks, and this class will call NEXT::check to pass
-the check "up the chain" (see L<NEXT>).
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->ut_foreign_keyn('parent_svcnum', 'cust_svc', 'svcnum')
- || $self->NEXT::check;
-
-}
-
-=back
-
-=head1 BUGS
-
-Do we need a recursive child finder for multi-layered children?
-
-=head1 SEE ALSO
-
-L<FS::svc_Common>, L<FS::Record>
-
-=cut
-
-1;
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
deleted file mode 100644
index 4343df5..0000000
--- a/FS/FS/svc_acct.pm
+++ /dev/null
@@ -1,2664 +0,0 @@
-package FS::svc_acct;
-
-use strict;
-use vars qw( @ISA $DEBUG $me $conf $skip_fuzzyfiles
- $dir_prefix @shells $usernamemin
- $usernamemax $passwordmin $passwordmax
- $username_ampersand $username_letter $username_letterfirst
- $username_noperiod $username_nounderscore $username_nodash
- $username_uppercase $username_percent
- $password_noampersand $password_noexclamation
- $warning_template $warning_from $warning_subject $warning_mimetype
- $warning_cc
- $smtpmachine
- $radius_password $radius_ip
- $dirhash
- @saltset @pw_set );
-use Carp;
-use Fcntl qw(:flock);
-use Date::Format;
-use Crypt::PasswdMD5 1.2;
-use Data::Dumper;
-use Authen::Passphrase;
-use FS::UID qw( datasrc driver_name );
-use FS::Conf;
-use FS::Record qw( qsearch qsearchs fields dbh dbdef );
-use FS::Msgcat qw(gettext);
-use FS::UI::bytecount;
-use FS::svc_Common;
-use FS::cust_svc;
-use FS::part_svc;
-use FS::svc_acct_pop;
-use FS::cust_main_invoice;
-use FS::svc_domain;
-use FS::raddb;
-use FS::queue;
-use FS::radius_usergroup;
-use FS::export_svc;
-use FS::part_export;
-use FS::svc_forward;
-use FS::svc_www;
-use FS::cdr;
-
-@ISA = qw( FS::svc_Common );
-
-$DEBUG = 0;
-$me = '[FS::svc_acct]';
-
-#ask FS::UID to run this stuff for us later
-$FS::UID::callback{'FS::svc_acct'} = sub {
- $conf = new FS::Conf;
- $dir_prefix = $conf->config('home');
- @shells = $conf->config('shells');
- $usernamemin = $conf->config('usernamemin') || 2;
- $usernamemax = $conf->config('usernamemax');
- $passwordmin = $conf->config('passwordmin') || 6;
- $passwordmax = $conf->config('passwordmax') || 8;
- $username_letter = $conf->exists('username-letter');
- $username_letterfirst = $conf->exists('username-letterfirst');
- $username_noperiod = $conf->exists('username-noperiod');
- $username_nounderscore = $conf->exists('username-nounderscore');
- $username_nodash = $conf->exists('username-nodash');
- $username_uppercase = $conf->exists('username-uppercase');
- $username_ampersand = $conf->exists('username-ampersand');
- $username_percent = $conf->exists('username-percent');
- $password_noampersand = $conf->exists('password-noexclamation');
- $password_noexclamation = $conf->exists('password-noexclamation');
- $dirhash = $conf->config('dirhash') || 0;
- if ( $conf->exists('warning_email') ) {
- $warning_template = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", $conf->config('warning_email') ]
- ) or warn "can't create warning email template: $Text::Template::ERROR";
- $warning_from = $conf->config('warning_email-from'); # || 'your-isp-is-dum'
- $warning_subject = $conf->config('warning_email-subject') || 'Warning';
- $warning_mimetype = $conf->config('warning_email-mimetype') || 'text/plain';
- $warning_cc = $conf->config('warning_email-cc');
- } else {
- $warning_template = '';
- $warning_from = '';
- $warning_subject = '';
- $warning_mimetype = '';
- $warning_cc = '';
- }
- $smtpmachine = $conf->config('smtpmachine');
- $radius_password = $conf->config('radius-password') || 'Password';
- $radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address';
- @pw_set = ( 'A'..'Z' ) if $conf->exists('password-generated-allcaps');
-};
-
-@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
-@pw_set = ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '!', '.', ',' );
-
-sub _cache {
- my $self = shift;
- my ( $hashref, $cache ) = @_;
- if ( $hashref->{'svc_acct_svcnum'} ) {
- $self->{'_domsvc'} = FS::svc_domain->new( {
- 'svcnum' => $hashref->{'domsvc'},
- 'domain' => $hashref->{'svc_acct_domain'},
- 'catchall' => $hashref->{'svc_acct_catchall'},
- } );
- }
-}
-
-=head1 NAME
-
-FS::svc_acct - Object methods for svc_acct records
-
-=head1 SYNOPSIS
-
- use FS::svc_acct;
-
- $record = new FS::svc_acct \%hash;
- $record = new FS::svc_acct { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $error = $record->cancel;
-
- %hash = $record->radius;
-
- %hash = $record->radius_reply;
-
- %hash = $record->radius_check;
-
- $domain = $record->domain;
-
- $svc_domain = $record->svc_domain;
-
- $email = $record->email;
-
- $seconds_since = $record->seconds_since($timestamp);
-
-=head1 DESCRIPTION
-
-An FS::svc_acct object represents an account. FS::svc_acct inherits from
-FS::svc_Common. The following fields are currently supported:
-
-=over 4
-
-=item svcnum - primary key (assigned automatcially for new accounts)
-
-=item username
-
-=item _password - generated if blank
-
-=item _password_encoding - plain, crypt, ldap (or empty for autodetection)
-
-=item sec_phrase - security phrase
-
-=item popnum - Point of presence (see L<FS::svc_acct_pop>)
-
-=item uid
-
-=item gid
-
-=item finger - GECOS
-
-=item dir - set automatically if blank (and uid is not)
-
-=item shell
-
-=item quota - (unimplementd)
-
-=item slipip - IP address
-
-=item seconds -
-
-=item upbytes -
-
-=item downbytes -
-
-=item totalbytes -
-
-=item domsvc - svcnum from svc_domain
-
-=item radius_I<Radius_Attribute> - I<Radius-Attribute> (reply)
-
-=item rc_I<Radius_Attribute> - I<Radius-Attribute> (check)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new account. To add the account to the database, see L<"insert">.
-
-=cut
-
-sub table_info {
- {
- 'name' => 'Account',
- 'longname_plural' => 'Access accounts and mailboxes',
- 'sorts' => [ 'username', 'uid', 'seconds', 'last_login' ],
- 'display_weight' => 10,
- 'cancel_weight' => 50,
- 'fields' => {
- 'dir' => 'Home directory',
- 'uid' => {
- label => 'UID',
- def_label => 'UID (set to fixed and blank for no UIDs)',
- type => 'text',
- },
- 'slipip' => 'IP address',
- # 'popnum' => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!,
- 'popnum' => {
- label => 'Access number',
- type => 'select',
- select_table => 'svc_acct_pop',
- select_key => 'popnum',
- select_label => 'city',
- disable_select => 1,
- },
- 'username' => {
- label => 'Username',
- type => 'text',
- disable_default => 1,
- disable_fixed => 1,
- disable_select => 1,
- },
- 'quota' => {
- label => 'Quota',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- },
- '_password' => 'Password',
- 'gid' => {
- label => 'GID',
- def_label => 'GID (when blank, defaults to UID)',
- type => 'text',
- },
- 'shell' => {
- #desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file, set to blank for no shell tracking)',
- label => 'Shell',
- def_label=> 'Shell (set to blank for no shell tracking)',
- type =>'select',
- select_list => [ $conf->config('shells') ],
- disable_inventory => 1,
- disable_select => 1,
- },
- 'finger' => 'Real name (GECOS)',
- 'domsvc' => {
- label => 'Domain',
- #def_label => 'svcnum from svc_domain',
- type => 'select',
- select_table => 'svc_domain',
- select_key => 'svcnum',
- select_label => 'domain',
- disable_inventory => 1,
-
- },
- 'usergroup' => {
- label => 'RADIUS groups',
- type => 'radius_usergroup_selector',
- disable_inventory => 1,
- disable_select => 1,
- },
- 'seconds' => { label => 'Seconds',
- label_sort => 'with Time Remaining',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- },
- 'upbytes' => { label => 'Upload',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'downbytes' => { label => 'Download',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'totalbytes'=> { label => 'Total up and download',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'seconds_threshold' => { label => 'Seconds threshold',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- },
- 'upbytes_threshold' => { label => 'Upload threshold',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'downbytes_threshold' => { label => 'Download threshold',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'totalbytes_threshold'=> { label => 'Total up and download threshold',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- 'format' => \&FS::UI::bytecount::display_bytecount,
- 'parse' => \&FS::UI::bytecount::parse_bytecount,
- },
- 'last_login'=> {
- label => 'Last login',
- type => 'disabled',
- },
- 'last_logout'=> {
- label => 'Last logout',
- type => 'disabled',
- },
- },
- };
-}
-
-sub table { 'svc_acct'; }
-
-sub _fieldhandlers {
- {
- #false laziness with edit/svc_acct.cgi
- 'usergroup' => sub {
- my( $self, $groups ) = @_;
- if ( ref($groups) eq 'ARRAY' ) {
- $groups;
- } elsif ( length($groups) ) {
- [ split(/\s*,\s*/, $groups) ];
- } else {
- [];
- }
- },
- };
-}
-
-sub last_login {
- shift->_lastlog('in', @_);
-}
-
-sub last_logout {
- shift->_lastlog('out', @_);
-}
-
-sub _lastlog {
- my( $self, $op, $time ) = @_;
-
- if ( defined($time) ) {
- warn "$me last_log$op called on svcnum ". $self->svcnum.
- ' ('. $self->email. "): $time\n"
- if $DEBUG;
-
- my $dbh = dbh;
-
- my $sql = "UPDATE svc_acct SET last_log$op = ? WHERE svcnum = ?";
- warn "$me $sql\n"
- if $DEBUG;
-
- my $sth = $dbh->prepare( $sql )
- or die "Error preparing $sql: ". $dbh->errstr;
- my $rv = $sth->execute($time, $self->svcnum);
- die "Error executing $sql: ". $sth->errstr
- unless defined($rv);
- die "Can't update last_log$op for svcnum". $self->svcnum
- if $rv == 0;
-
- $self->{'Hash'}->{"last_log$op"} = $time;
- }else{
- $self->getfield("last_log$op");
- }
-}
-
-=item search_sql STRING
-
-Class method which returns an SQL fragment to search for the given string.
-
-=cut
-
-sub search_sql {
- my( $class, $string ) = @_;
- if ( $string =~ /^([^@]+)@([^@]+)$/ ) {
- my( $username, $domain ) = ( $1, $2 );
- my $q_username = dbh->quote($username);
- my @svc_domain = qsearch('svc_domain', { 'domain' => $domain } );
- if ( @svc_domain ) {
- "svc_acct.username = $q_username AND ( ".
- join( ' OR ', map { "svc_acct.domsvc = ". $_->svcnum; } @svc_domain ).
- " )";
- } else {
- '1 = 0'; #false
- }
- } elsif ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
- ' ( '.
- $class->search_sql_field('slipip', $string ).
- ' OR '.
- $class->search_sql_field('username', $string ).
- ' ) ';
- } else {
- $class->search_sql_field('username', $string);
- }
-}
-
-=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ]
-
-Returns the "username@domain" string for this account.
-
-END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
-history records.
-
-=cut
-
-sub label {
- my $self = shift;
- $self->email(@_);
-}
-
-=cut
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this account to the database. If there is an error, returns the error,
-otherwise returns false.
-
-The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
-defined. An FS::cust_svc record will be created and inserted.
-
-The additional field I<usergroup> can optionally be defined; if so it should
-contain an arrayref of group names. See L<FS::radius_usergroup>.
-
-The additional field I<child_objects> can optionally be defined; if so it
-should contain an arrayref of FS::tablename objects. They will have their
-svcnum fields set and will be inserted after this record, but before any
-exports are run. Each element of the array can also optionally be a
-two-element array reference containing the child object and the name of an
-alternate field to be filled in with the newly-inserted svcnum, for example
-C<[ $svc_forward, 'srcsvc' ]>
-
-Currently available options are: I<depend_jobnum>
-
-If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
-jobnums), all provisioning jobs will have a dependancy on the supplied
-jobnum(s) (they will not run until the specific job(s) complete(s)).
-
-(TODOC: L<FS::queue> and L<freeside-queued>)
-
-(TODOC: new exports!)
-
-=cut
-
-sub insert {
- my $self = shift;
- my %options = @_;
-
- if ( $DEBUG ) {
- warn "[$me] insert called on $self: ". Dumper($self).
- "\nwith options: ". Dumper(%options);
- }
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $self->check;
- return $error if $error;
-
- if ( $self->svcnum && qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) ) {
- my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
- unless ( $cust_svc ) {
- $dbh->rollback if $oldAutoCommit;
- return "no cust_svc record found for svcnum ". $self->svcnum;
- }
- $self->pkgnum($cust_svc->pkgnum);
- $self->svcpart($cust_svc->svcpart);
- }
-
- $error = $self->_check_duplicate;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- my @jobnums;
- $error = $self->SUPER::insert(
- 'jobnums' => \@jobnums,
- 'child_objects' => $self->child_objects,
- %options,
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $self->usergroup ) {
- foreach my $groupname ( @{$self->usergroup} ) {
- my $radius_usergroup = new FS::radius_usergroup ( {
- svcnum => $self->svcnum,
- groupname => $groupname,
- } );
- my $error = $radius_usergroup->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
- unless ( $skip_fuzzyfiles ) {
- $error = $self->queue_fuzzyfiles_update;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "updating fuzzy search cache: $error";
- }
- }
-
- my $cust_pkg = $self->cust_svc->cust_pkg;
-
- if ( $cust_pkg ) {
- my $cust_main = $cust_pkg->cust_main;
- my $agentnum = $cust_main->agentnum;
-
- if ( $conf->exists('emailinvoiceautoalways')
- || $conf->exists('emailinvoiceauto')
- && ! $cust_main->invoicing_list_emailonly
- ) {
- my @invoicing_list = $cust_main->invoicing_list;
- push @invoicing_list, $self->email;
- $cust_main->invoicing_list(\@invoicing_list);
- }
-
- #welcome email
- my ($to,$welcome_template,$welcome_from,$welcome_subject,$welcome_subject_template,$welcome_mimetype)
- = ('','','','','','');
-
- if ( $conf->exists('welcome_email', $agentnum) ) {
- $welcome_template = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", $conf->config('welcome_email', $agentnum) ]
- ) or warn "can't create welcome email template: $Text::Template::ERROR";
- $welcome_from = $conf->config('welcome_email-from', $agentnum);
- # || 'your-isp-is-dum'
- $welcome_subject = $conf->config('welcome_email-subject', $agentnum)
- || 'Welcome';
- $welcome_subject_template = new Text::Template (
- TYPE => 'STRING',
- SOURCE => $welcome_subject,
- ) or warn "can't create welcome email subject template: $Text::Template::ERROR";
- $welcome_mimetype = $conf->config('welcome_email-mimetype', $agentnum)
- || 'text/plain';
- }
- if ( $welcome_template && $cust_pkg ) {
- my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
- if ( $to ) {
-
- my %hash = (
- 'custnum' => $self->custnum,
- 'username' => $self->username,
- 'password' => $self->_password,
- 'first' => $cust_main->first,
- 'last' => $cust_main->getfield('last'),
- 'pkg' => $cust_pkg->part_pkg->pkg,
- );
- my $wqueue = new FS::queue {
- 'svcnum' => $self->svcnum,
- 'job' => 'FS::svc_acct::send_email'
- };
- my $error = $wqueue->insert(
- 'to' => $to,
- 'from' => $welcome_from,
- 'subject' => $welcome_subject_template->fill_in( HASH => \%hash, ),
- 'mimetype' => $welcome_mimetype,
- 'body' => $welcome_template->fill_in( HASH => \%hash, ),
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error queuing welcome email: $error";
- }
-
- if ( $options{'depend_jobnum'} ) {
- warn "$me depend_jobnum found; adding to welcome email dependancies"
- if $DEBUG;
- if ( ref($options{'depend_jobnum'}) ) {
- warn "$me adding jobs ". join(', ', @{$options{'depend_jobnum'}} ).
- "to welcome email dependancies"
- if $DEBUG;
- push @jobnums, @{ $options{'depend_jobnum'} };
- } else {
- warn "$me adding job $options{'depend_jobnum'} ".
- "to welcome email dependancies"
- if $DEBUG;
- push @jobnums, $options{'depend_jobnum'};
- }
- }
-
- foreach my $jobnum ( @jobnums ) {
- my $error = $wqueue->depend_insert($jobnum);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error queuing welcome email job dependancy: $error";
- }
- }
-
- }
-
- }
-
- } # if ( $cust_pkg )
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-}
-
-=item delete
-
-Deletes this account from the database. If there is an error, returns the
-error, otherwise returns false.
-
-The corresponding FS::cust_svc record will be deleted as well.
-
-(TODOC: new exports!)
-
-=cut
-
-sub delete {
- my $self = shift;
-
- return "can't delete system account" if $self->_check_system;
-
- return "Can't delete an account which is a (svc_forward) source!"
- if qsearch( 'svc_forward', { 'srcsvc' => $self->svcnum } );
-
- return "Can't delete an account which is a (svc_forward) destination!"
- if qsearch( 'svc_forward', { 'dstsvc' => $self->svcnum } );
-
- return "Can't delete an account with (svc_www) web service!"
- if qsearch( 'svc_www', { 'usersvc' => $self->svcnum } );
-
- # what about records in session ? (they should refer to history table)
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $cust_main_invoice (
- qsearch( 'cust_main_invoice', { 'dest' => $self->svcnum } )
- ) {
- unless ( defined($cust_main_invoice) ) {
- warn "WARNING: something's wrong with qsearch";
- next;
- }
- my %hash = $cust_main_invoice->hash;
- $hash{'dest'} = $self->email;
- my $new = new FS::cust_main_invoice \%hash;
- my $error = $new->replace($cust_main_invoice);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- foreach my $svc_domain (
- qsearch( 'svc_domain', { 'catchall' => $self->svcnum } )
- ) {
- my %hash = new FS::svc_domain->hash;
- $hash{'catchall'} = '';
- my $new = new FS::svc_domain \%hash;
- my $error = $new->replace($svc_domain);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- foreach my $radius_usergroup (
- qsearch('radius_usergroup', { 'svcnum' => $self->svcnum } )
- ) {
- my $error = $radius_usergroup->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-The additional field I<usergroup> can optionally be defined; if so it should
-contain an arrayref of group names. See L<FS::radius_usergroup>.
-
-
-=cut
-
-sub replace {
- my ( $new, $old ) = ( shift, shift );
- my $error;
- warn "$me replacing $old with $new\n" if $DEBUG;
-
- # We absolutely have to have an old vs. new record to make this work.
- if (!defined($old)) {
- $old = qsearchs( 'svc_acct', { 'svcnum' => $new->svcnum } );
- }
-
- return "can't modify system account" if $old->_check_system;
-
- {
- #no warnings 'numeric'; #alas, a 5.006-ism
- local($^W) = 0;
-
- foreach my $xid (qw( uid gid )) {
-
- return "Can't change $xid!"
- if ! $conf->exists("svc_acct-edit_$xid")
- && $old->$xid() != $new->$xid()
- && $new->cust_svc->part_svc->part_svc_column($xid)->columnflag ne 'F'
- }
-
- }
-
- #change homdir when we change username
- $new->setfield('dir', '') if $old->username ne $new->username;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- # redundant, but so $new->usergroup gets set
- $error = $new->check;
- return $error if $error;
-
- $old->usergroup( [ $old->radius_groups ] );
- if ( $DEBUG ) {
- warn $old->email. " old groups: ". join(' ',@{$old->usergroup}). "\n";
- warn $new->email. "new groups: ". join(' ',@{$new->usergroup}). "\n";
- }
- if ( $new->usergroup ) {
- #(sorta) false laziness with FS::part_export::sqlradius::_export_replace
- my @newgroups = @{$new->usergroup};
- foreach my $oldgroup ( @{$old->usergroup} ) {
- if ( grep { $oldgroup eq $_ } @newgroups ) {
- @newgroups = grep { $oldgroup ne $_ } @newgroups;
- next;
- }
- my $radius_usergroup = qsearchs('radius_usergroup', {
- svcnum => $old->svcnum,
- groupname => $oldgroup,
- } );
- my $error = $radius_usergroup->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error deleting radius_usergroup $oldgroup: $error";
- }
- }
-
- foreach my $newgroup ( @newgroups ) {
- my $radius_usergroup = new FS::radius_usergroup ( {
- svcnum => $new->svcnum,
- groupname => $newgroup,
- } );
- my $error = $radius_usergroup->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error adding radius_usergroup $newgroup: $error";
- }
- }
-
- }
-
- if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
- $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart;
- $error = $new->_check_duplicate;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $error = $new->SUPER::replace($old, @_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error if $error;
- }
-
- if ( $new->username ne $old->username && ! $skip_fuzzyfiles ) {
- $error = $new->queue_fuzzyfiles_update;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "updating fuzzy search cache: $error";
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-}
-
-=item queue_fuzzyfiles_update
-
-Used by insert & replace to update the fuzzy search cache
-
-=cut
-
-sub queue_fuzzyfiles_update {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $queue = new FS::queue {
- 'svcnum' => $self->svcnum,
- 'job' => 'FS::svc_acct::append_fuzzyfiles'
- };
- my $error = $queue->insert($self->username);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "queueing job (transaction rolled back): $error";
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-
-=item suspend
-
-Suspends this account by calling export-specific suspend hooks. If there is
-an error, returns the error, otherwise returns false.
-
-Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=cut
-
-sub suspend {
- my $self = shift;
- return "can't suspend system account" if $self->_check_system;
- $self->SUPER::suspend(@_);
-}
-
-=item unsuspend
-
-Unsuspends this account by by calling export-specific suspend hooks. If there
-is an error, returns the error, otherwise returns false.
-
-Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=cut
-
-sub unsuspend {
- my $self = shift;
- my %hash = $self->hash;
- if ( $hash{_password} =~ /^\*SUSPENDED\* (.*)$/ ) {
- $hash{_password} = $1;
- my $new = new FS::svc_acct ( \%hash );
- my $error = $new->replace($self);
- return $error if $error;
- }
-
- $self->SUPER::unsuspend(@_);
-}
-
-=item cancel
-
-Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-If the B<auto_unset_catchall> configuration option is set, this method will
-automatically remove any references to the canceled service in the catchall
-field of svc_domain. This allows packages that contain both a svc_domain and
-its catchall svc_acct to be canceled in one step.
-
-=cut
-
-sub cancel {
- # Only one thing to do at this level
- my $self = shift;
- foreach my $svc_domain (
- qsearch( 'svc_domain', { catchall => $self->svcnum } ) ) {
- if($conf->exists('auto_unset_catchall')) {
- my %hash = $svc_domain->hash;
- $hash{catchall} = '';
- my $new = new FS::svc_domain ( \%hash );
- my $error = $new->replace($svc_domain);
- return $error if $error;
- } else {
- return "cannot unprovision svc_acct #".$self->svcnum.
- " while assigned as catchall for svc_domain #".$svc_domain->svcnum;
- }
- }
-
- $self->SUPER::cancel(@_);
-}
-
-
-=item check
-
-Checks all fields to make sure this is a valid service. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-Sets any fixed values; see L<FS::part_svc>.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my($recref) = $self->hashref;
-
- my $x = $self->setfixed( $self->_fieldhandlers );
- return $x unless ref($x);
- my $part_svc = $x;
-
- if ( $part_svc->part_svc_column('usergroup')->columnflag eq "F" ) {
- $self->usergroup(
- [ split(',', $part_svc->part_svc_column('usergroup')->columnvalue) ] );
- }
-
- my $error = $self->ut_numbern('svcnum')
- #|| $self->ut_number('domsvc')
- || $self->ut_foreign_key('domsvc', 'svc_domain', 'svcnum' )
- || $self->ut_textn('sec_phrase')
- || $self->ut_snumbern('seconds')
- || $self->ut_snumbern('upbytes')
- || $self->ut_snumbern('downbytes')
- || $self->ut_snumbern('totalbytes')
- || $self->ut_enum( '_password_encoding',
- [ '', qw( plain crypt ldap ) ]
- )
- ;
- return $error if $error;
-
- my $ulen = $usernamemax || $self->dbdef_table->column('username')->length;
- if ( $username_uppercase ) {
- $recref->{username} =~ /^([a-z0-9_\-\.\&\%]{$usernamemin,$ulen})$/i
- or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
- $recref->{username} = $1;
- } else {
- $recref->{username} =~ /^([a-z0-9_\-\.\&\%]{$usernamemin,$ulen})$/
- or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
- $recref->{username} = $1;
- }
-
- if ( $username_letterfirst ) {
- $recref->{username} =~ /^[a-z]/ or return gettext('illegal_username');
- } elsif ( $username_letter ) {
- $recref->{username} =~ /[a-z]/ or return gettext('illegal_username');
- }
- if ( $username_noperiod ) {
- $recref->{username} =~ /\./ and return gettext('illegal_username');
- }
- if ( $username_nounderscore ) {
- $recref->{username} =~ /_/ and return gettext('illegal_username');
- }
- if ( $username_nodash ) {
- $recref->{username} =~ /\-/ and return gettext('illegal_username');
- }
- unless ( $username_ampersand ) {
- $recref->{username} =~ /\&/ and return gettext('illegal_username');
- }
- unless ( $username_percent ) {
- $recref->{username} =~ /\%/ and return gettext('illegal_username');
- }
-
- $recref->{popnum} =~ /^(\d*)$/ or return "Illegal popnum: ".$recref->{popnum};
- $recref->{popnum} = $1;
- return "Unknown popnum" unless
- ! $recref->{popnum} ||
- qsearchs('svc_acct_pop',{'popnum'=> $recref->{popnum} } );
-
- unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
-
- $recref->{uid} =~ /^(\d*)$/ or return "Illegal uid";
- $recref->{uid} = $1 eq '' ? $self->unique('uid') : $1;
-
- $recref->{gid} =~ /^(\d*)$/ or return "Illegal gid";
- $recref->{gid} = $1 eq '' ? $recref->{uid} : $1;
- #not all systems use gid=uid
- #you can set a fixed gid in part_svc
-
- return "Only root can have uid 0"
- if $recref->{uid} == 0
- && $recref->{username} !~ /^(root|toor|smtp)$/;
-
- unless ( $recref->{username} eq 'sync' ) {
- if ( grep $_ eq $recref->{shell}, @shells ) {
- $recref->{shell} = (grep $_ eq $recref->{shell}, @shells)[0];
- } else {
- return "Illegal shell \`". $self->shell. "\'; ".
- "shells configuration value contains: @shells";
- }
- } else {
- $recref->{shell} = '/bin/sync';
- }
-
- } else {
- $recref->{gid} ne '' ?
- return "Can't have gid without uid" : ( $recref->{gid}='' );
- #$recref->{dir} ne '' ?
- # return "Can't have directory without uid" : ( $recref->{dir}='' );
- $recref->{shell} ne '' ?
- return "Can't have shell without uid" : ( $recref->{shell}='' );
- }
-
- unless ( $part_svc->part_svc_column('dir')->columnflag eq 'F' ) {
-
- $recref->{dir} =~ /^([\/\w\-\.\&]*)$/
- or return "Illegal directory: ". $recref->{dir};
- $recref->{dir} = $1;
- return "Illegal directory"
- if $recref->{dir} =~ /(^|\/)\.+(\/|$)/; #no .. component
- return "Illegal directory"
- if $recref->{dir} =~ /\&/ && ! $username_ampersand;
- unless ( $recref->{dir} ) {
- $recref->{dir} = $dir_prefix . '/';
- if ( $dirhash > 0 ) {
- for my $h ( 1 .. $dirhash ) {
- $recref->{dir} .= substr($recref->{username}, $h-1, 1). '/';
- }
- } elsif ( $dirhash < 0 ) {
- for my $h ( reverse $dirhash .. -1 ) {
- $recref->{dir} .= substr($recref->{username}, $h, 1). '/';
- }
- }
- $recref->{dir} .= $recref->{username};
- ;
- }
-
- }
-
- # $error = $self->ut_textn('finger');
- # return $error if $error;
- if ( $self->getfield('finger') eq '' ) {
- my $cust_pkg = $self->svcnum
- ? $self->cust_svc->cust_pkg
- : qsearchs('cust_pkg', { 'pkgnum' => $self->getfield('pkgnum') } );
- if ( $cust_pkg ) {
- my $cust_main = $cust_pkg->cust_main;
- $self->setfield('finger', $cust_main->first.' '.$cust_main->get('last') );
- }
- }
- $self->getfield('finger') =~
- /^([\w \t\!\@\#\$\%\&\(\)\-\+\;\'\"\,\.\?\/\*\<\>]*)$/
- or return "Illegal finger: ". $self->getfield('finger');
- $self->setfield('finger', $1);
-
- $recref->{quota} =~ /^(\w*)$/ or return "Illegal quota";
- $recref->{quota} = $1;
-
- unless ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) {
- if ( $recref->{slipip} eq '' ) {
- $recref->{slipip} = '';
- } elsif ( $recref->{slipip} eq '0e0' ) {
- $recref->{slipip} = '0e0';
- } else {
- $recref->{slipip} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/
- or return "Illegal slipip: ". $self->slipip;
- $recref->{slipip} = $1;
- }
-
- }
-
- #arbitrary RADIUS stuff; allow ut_textn for now
- foreach ( grep /^radius_/, fields('svc_acct') ) {
- $self->ut_textn($_);
- }
-
- if ( $recref->{_password_encoding} eq 'ldap' ) {
-
- if ( $recref->{_password} =~ /^(\{[\w\-]+\})(!?.{0,64})$/ ) {
- $recref->{_password} = uc($1).$2;
- } else {
- return 'Illegal (ldap-encoded) password: '. $recref->{_password};
- }
-
- } elsif ( $recref->{_password_encoding} eq 'crypt' ) {
-
- if ( $recref->{_password} =~
- #/^(\$\w+\$.*|[\w\+\/]{13}|_[\w\+\/]{19}|\*)$/
- /^(!!?)?(\$\w+\$.*|[\w\+\/]{13}|_[\w\+\/]{19}|\*)$/
- ) {
-
- $recref->{_password} = $1.$2;
-
- } else {
- return 'Illegal (crypt-encoded) password';
- }
-
- } elsif ( $recref->{_password_encoding} eq 'plain' ) {
-
- #generate a password if it is blank
- $recref->{_password} = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) )
- unless length( $recref->{_password} );
-
- if ( $recref->{_password} =~ /^([^\t\n]{$passwordmin,$passwordmax})$/ ) {
- $recref->{_password} = $1;
- } else {
- return gettext('illegal_password'). " $passwordmin-$passwordmax ".
- FS::Msgcat::_gettext('illegal_password_characters').
- ": ". $recref->{_password};
- }
-
- if ( $password_noampersand ) {
- $recref->{_password} =~ /\&/ and return gettext('illegal_password');
- }
- if ( $password_noexclamation ) {
- $recref->{_password} =~ /\!/ and return gettext('illegal_password');
- }
-
- } else {
-
- #carp "warning: _password_encoding unspecified\n";
-
- #generate a password if it is blank
- unless ( length( $recref->{_password} ) ) {
-
- $recref->{_password} =
- join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
- $recref->{_password_encoding} = 'plain';
-
- } else {
-
- #if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{4,16})$/ ) {
- if ( $recref->{_password} =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) {
- $recref->{_password} = $1.$3;
- $recref->{_password_encoding} = 'plain';
- } elsif ( $recref->{_password} =~
- /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/
- ) {
- $recref->{_password} = $1.$3;
- $recref->{_password_encoding} = 'crypt';
- } elsif ( $recref->{_password} eq '*' ) {
- $recref->{_password} = '*';
- $recref->{_password_encoding} = 'crypt';
- } elsif ( $recref->{_password} eq '!' ) {
- $recref->{_password_encoding} = 'crypt';
- $recref->{_password} = '!';
- } elsif ( $recref->{_password} eq '!!' ) {
- $recref->{_password} = '!!';
- $recref->{_password_encoding} = 'crypt';
- } else {
- #return "Illegal password";
- return gettext('illegal_password'). " $passwordmin-$passwordmax ".
- FS::Msgcat::_gettext('illegal_password_characters').
- ": ". $recref->{_password};
- }
-
- }
-
- }
-
- $self->SUPER::check;
-
-}
-
-=item _check_system
-
-Internal function to check the username against the list of system usernames
-from the I<system_usernames> configuration value. Returns true if the username
-is listed on the system username list.
-
-=cut
-
-sub _check_system {
- my $self = shift;
- scalar( grep { $self->username eq $_ || $self->email eq $_ }
- $conf->config('system_usernames')
- );
-}
-
-=item _check_duplicate
-
-Internal function to check for duplicates usernames, username@domain pairs and
-uids.
-
-If the I<global_unique-username> configuration value is set to B<username> or
-B<username@domain>, enforces global username or username@domain uniqueness.
-
-In all cases, check for duplicate uids and usernames or username@domain pairs
-per export and with identical I<svcpart> values.
-
-=cut
-
-sub _check_duplicate {
- my $self = shift;
-
- my $global_unique = $conf->config('global_unique-username') || 'none';
- return '' if $global_unique eq 'disabled';
-
- warn "$me locking svc_acct table for duplicate search" if $DEBUG;
- if ( driver_name =~ /^Pg/i ) {
- dbh->do("LOCK TABLE svc_acct IN SHARE ROW EXCLUSIVE MODE")
- or die dbh->errstr;
- } elsif ( driver_name =~ /^mysql/i ) {
- dbh->do("SELECT * FROM duplicate_lock
- WHERE lockname = 'svc_acct'
- FOR UPDATE"
- ) or die dbh->errstr;
- } else {
- die "unknown database ". driver_name.
- "; don't know how to lock for duplicate search";
- }
- warn "$me acquired svc_acct table lock for duplicate search" if $DEBUG;
-
- my $part_svc = qsearchs('part_svc', { 'svcpart' => $self->svcpart } );
- unless ( $part_svc ) {
- return 'unknown svcpart '. $self->svcpart;
- }
-
- my @dup_user = grep { !$self->svcnum || $_->svcnum != $self->svcnum }
- qsearch( 'svc_acct', { 'username' => $self->username } );
- return gettext('username_in_use')
- if $global_unique eq 'username' && @dup_user;
-
- my @dup_userdomain = grep { !$self->svcnum || $_->svcnum != $self->svcnum }
- qsearch( 'svc_acct', { 'username' => $self->username,
- 'domsvc' => $self->domsvc } );
- return gettext('username_in_use')
- if $global_unique eq 'username@domain' && @dup_userdomain;
-
- my @dup_uid;
- if ( $part_svc->part_svc_column('uid')->columnflag ne 'F'
- && $self->username !~ /^(toor|(hyla)?fax)$/ ) {
- @dup_uid = grep { !$self->svcnum || $_->svcnum != $self->svcnum }
- qsearch( 'svc_acct', { 'uid' => $self->uid } );
- } else {
- @dup_uid = ();
- }
-
- if ( @dup_user || @dup_userdomain || @dup_uid ) {
- my $exports = FS::part_export::export_info('svc_acct');
- my %conflict_user_svcpart;
- my %conflict_userdomain_svcpart = ( $self->svcpart => 'SELF', );
-
- foreach my $part_export ( $part_svc->part_export ) {
-
- #this will catch to the same exact export
- my @svcparts = map { $_->svcpart } $part_export->export_svc;
-
- #this will catch to exports w/same exporthost+type ???
- #my @other_part_export = qsearch('part_export', {
- # 'machine' => $part_export->machine,
- # 'exporttype' => $part_export->exporttype,
- #} );
- #foreach my $other_part_export ( @other_part_export ) {
- # push @svcparts, map { $_->svcpart }
- # qsearch('export_svc', { 'exportnum' => $part_export->exportnum });
- #}
-
- #my $nodomain = $exports->{$part_export->exporttype}{'nodomain'};
- #silly kludge to avoid uninitialized value errors
- my $nodomain = exists( $exports->{$part_export->exporttype}{'nodomain'} )
- ? $exports->{$part_export->exporttype}{'nodomain'}
- : '';
- if ( $nodomain =~ /^Y/i ) {
- $conflict_user_svcpart{$_} = $part_export->exportnum
- foreach @svcparts;
- } else {
- $conflict_userdomain_svcpart{$_} = $part_export->exportnum
- foreach @svcparts;
- }
- }
-
- foreach my $dup_user ( @dup_user ) {
- my $dup_svcpart = $dup_user->cust_svc->svcpart;
- if ( exists($conflict_user_svcpart{$dup_svcpart}) ) {
- return "duplicate username: conflicts with svcnum ". $dup_user->svcnum.
- " via exportnum ". $conflict_user_svcpart{$dup_svcpart};
- }
- }
-
- foreach my $dup_userdomain ( @dup_userdomain ) {
- my $dup_svcpart = $dup_userdomain->cust_svc->svcpart;
- if ( exists($conflict_userdomain_svcpart{$dup_svcpart}) ) {
- return "duplicate username\@domain: conflicts with svcnum ".
- $dup_userdomain->svcnum. " via exportnum ".
- $conflict_userdomain_svcpart{$dup_svcpart};
- }
- }
-
- foreach my $dup_uid ( @dup_uid ) {
- my $dup_svcpart = $dup_uid->cust_svc->svcpart;
- if ( exists($conflict_user_svcpart{$dup_svcpart})
- || exists($conflict_userdomain_svcpart{$dup_svcpart}) ) {
- return "duplicate uid: conflicts with svcnum ". $dup_uid->svcnum.
- " via exportnum ". $conflict_user_svcpart{$dup_svcpart}
- || $conflict_userdomain_svcpart{$dup_svcpart};
- }
- }
-
- }
-
- return '';
-
-}
-
-=item radius
-
-Depriciated, use radius_reply instead.
-
-=cut
-
-sub radius {
- carp "FS::svc_acct::radius depriciated, use radius_reply";
- $_[0]->radius_reply;
-}
-
-=item radius_reply
-
-Returns key/value pairs, suitable for assigning to a hash, for any RADIUS
-reply attributes of this record.
-
-Note that this is now the preferred method for reading RADIUS attributes -
-accessing the columns directly is discouraged, as the column names are
-expected to change in the future.
-
-=cut
-
-sub radius_reply {
- my $self = shift;
-
- return %{ $self->{'radius_reply'} }
- if exists $self->{'radius_reply'};
-
- my %reply =
- map {
- /^(radius_(.*))$/;
- my($column, $attrib) = ($1, $2);
- #$attrib =~ s/_/\-/g;
- ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) );
- } grep { /^radius_/ && $self->getfield($_) } fields( $self->table );
-
- if ( $self->slipip && $self->slipip ne '0e0' ) {
- $reply{$radius_ip} = $self->slipip;
- }
-
- if ( $self->seconds !~ /^$/ ) {
- $reply{'Session-Timeout'} = $self->seconds;
- }
-
- %reply;
-}
-
-=item radius_check
-
-Returns key/value pairs, suitable for assigning to a hash, for any RADIUS
-check attributes of this record.
-
-Note that this is now the preferred method for reading RADIUS attributes -
-accessing the columns directly is discouraged, as the column names are
-expected to change in the future.
-
-=cut
-
-sub radius_check {
- my $self = shift;
-
- return %{ $self->{'radius_check'} }
- if exists $self->{'radius_check'};
-
- my %check =
- map {
- /^(rc_(.*))$/;
- my($column, $attrib) = ($1, $2);
- #$attrib =~ s/_/\-/g;
- ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) );
- } grep { /^rc_/ && $self->getfield($_) } fields( $self->table );
-
- my $password = $self->_password;
- my $pw_attrib = length($password) <= 12 ? $radius_password : 'Crypt-Password'; $check{$pw_attrib} = $password;
-
- my $cust_svc = $self->cust_svc;
- die "FATAL: no cust_svc record for svc_acct.svcnum ". $self->svcnum. "\n"
- unless $cust_svc;
- my $cust_pkg = $cust_svc->cust_pkg;
- if ( $cust_pkg && $cust_pkg->part_pkg->is_prepaid && $cust_pkg->bill ) {
- $check{'Expiration'} = time2str('%B %e %Y %T', $cust_pkg->bill ); #http://lists.cistron.nl/pipermail/freeradius-users/2005-January/040184.html
- }
-
- %check;
-
-}
-
-=item snapshot
-
-This method instructs the object to "snapshot" or freeze RADIUS check and
-reply attributes to the current values.
-
-=cut
-
-#bah, my english is too broken this morning
-#Of note is the "Expiration" attribute, which, for accounts in prepaid packages, is typically defined on-the-fly as the associated packages cust_pkg.bill. (This is used by
-#the FS::cust_pkg's replace method to trigger the correct export updates when
-#package dates change)
-
-sub snapshot {
- my $self = shift;
-
- $self->{$_} = { $self->$_() }
- foreach qw( radius_reply radius_check );
-
-}
-
-=item forget_snapshot
-
-This methos instructs the object to forget any previously snapshotted
-RADIUS check and reply attributes.
-
-=cut
-
-sub forget_snapshot {
- my $self = shift;
-
- delete $self->{$_}
- foreach qw( radius_reply radius_check );
-
-}
-
-=item domain [ END_TIMESTAMP [ START_TIMESTAMP ] ]
-
-Returns the domain associated with this account.
-
-END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
-history records.
-
-=cut
-
-sub domain {
- my $self = shift;
- die "svc_acct.domsvc is null for svcnum ". $self->svcnum unless $self->domsvc;
- my $svc_domain = $self->svc_domain(@_)
- or die "no svc_domain.svcnum for svc_acct.domsvc ". $self->domsvc;
- $svc_domain->domain;
-}
-
-=item svc_domain
-
-Returns the FS::svc_domain record for this account's domain (see
-L<FS::svc_domain>).
-
-=cut
-
-# FS::h_svc_acct has a history-aware svc_domain override
-
-sub svc_domain {
- my $self = shift;
- $self->{'_domsvc'}
- ? $self->{'_domsvc'}
- : qsearchs( 'svc_domain', { 'svcnum' => $self->domsvc } );
-}
-
-=item cust_svc
-
-Returns the FS::cust_svc record for this account (see L<FS::cust_svc>).
-
-=cut
-
-#inherited from svc_Common
-
-=item email [ END_TIMESTAMP [ START_TIMESTAMP ] ]
-
-Returns an email address associated with the account.
-
-END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
-history records.
-
-=cut
-
-sub email {
- my $self = shift;
- $self->username. '@'. $self->domain(@_);
-}
-
-=item acct_snarf
-
-Returns an array of FS::acct_snarf records associated with the account.
-If the acct_snarf table does not exist or there are no associated records,
-an empty list is returned
-
-=cut
-
-sub acct_snarf {
- my $self = shift;
- return () unless dbdef->table('acct_snarf');
- eval "use FS::acct_snarf;";
- die $@ if $@;
- qsearch('acct_snarf', { 'svcnum' => $self->svcnum } );
-}
-
-=item decrement_upbytes OCTETS
-
-Decrements the I<upbytes> field of this record by the given amount. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub decrement_upbytes {
- shift->_op_usage('-', 'upbytes', @_);
-}
-
-=item increment_upbytes OCTETS
-
-Increments the I<upbytes> field of this record by the given amount. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub increment_upbytes {
- shift->_op_usage('+', 'upbytes', @_);
-}
-
-=item decrement_downbytes OCTETS
-
-Decrements the I<downbytes> field of this record by the given amount. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub decrement_downbytes {
- shift->_op_usage('-', 'downbytes', @_);
-}
-
-=item increment_downbytes OCTETS
-
-Increments the I<downbytes> field of this record by the given amount. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub increment_downbytes {
- shift->_op_usage('+', 'downbytes', @_);
-}
-
-=item decrement_totalbytes OCTETS
-
-Decrements the I<totalbytes> field of this record by the given amount. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub decrement_totalbytes {
- shift->_op_usage('-', 'totalbytes', @_);
-}
-
-=item increment_totalbytes OCTETS
-
-Increments the I<totalbytes> field of this record by the given amount. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub increment_totalbytes {
- shift->_op_usage('+', 'totalbytes', @_);
-}
-
-=item decrement_seconds SECONDS
-
-Decrements the I<seconds> field of this record by the given amount. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub decrement_seconds {
- shift->_op_usage('-', 'seconds', @_);
-}
-
-=item increment_seconds SECONDS
-
-Increments the I<seconds> field of this record by the given amount. If there
-is an error, returns the error, otherwise returns false.
-
-=cut
-
-sub increment_seconds {
- shift->_op_usage('+', 'seconds', @_);
-}
-
-
-my %op2action = (
- '-' => 'suspend',
- '+' => 'unsuspend',
-);
-my %op2condition = (
- '-' => sub { my($self, $column, $amount) = @_;
- $self->$column - $amount <= 0;
- },
- '+' => sub { my($self, $column, $amount) = @_;
- $self->$column + $amount > 0;
- },
-);
-my %op2warncondition = (
- '-' => sub { my($self, $column, $amount) = @_;
- my $threshold = $column . '_threshold';
- $self->$column - $amount <= $self->$threshold + 0;
- },
- '+' => sub { my($self, $column, $amount) = @_;
- $self->$column + $amount > 0;
- },
-);
-
-sub _op_usage {
- my( $self, $op, $column, $amount ) = @_;
-
- warn "$me _op_usage called for $column on svcnum ". $self->svcnum.
- ' ('. $self->email. "): $op $amount\n"
- if $DEBUG;
-
- return '' unless $amount;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $sql = "UPDATE svc_acct SET $column = ".
- " CASE WHEN $column IS NULL THEN 0 ELSE $column END ". #$column||0
- " $op ? WHERE svcnum = ?";
- warn "$me $sql\n"
- if $DEBUG;
-
- my $sth = $dbh->prepare( $sql )
- or die "Error preparing $sql: ". $dbh->errstr;
- my $rv = $sth->execute($amount, $self->svcnum);
- die "Error executing $sql: ". $sth->errstr
- unless defined($rv);
- die "Can't update $column for svcnum". $self->svcnum
- if $rv == 0;
-
- my $action = $op2action{$op};
-
- if ( &{$op2condition{$op}}($self, $column, $amount) &&
- ( $action eq 'suspend' && !$self->overlimit
- || $action eq 'unsuspend' && $self->overlimit )
- ) {
- foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
- if ($part_export->option('overlimit_groups')) {
- my ($new,$old);
- my $other = new FS::svc_acct $self->hashref;
- my $groups = &{ $self->_fieldhandlers->{'usergroup'} }
- ($self, $part_export->option('overlimit_groups'));
- $other->usergroup( $groups );
- if ($action eq 'suspend'){
- $new = $other; $old = $self;
- }else{
- $new = $self; $old = $other;
- }
- my $error = $part_export->export_replace($new, $old);
- $error ||= $self->overlimit($action);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error replacing radius groups in export, ${op}: $error";
- }
- }
- }
- }
-
- if ( $conf->exists("svc_acct-usage_$action")
- && &{$op2condition{$op}}($self, $column, $amount) ) {
- #my $error = $self->$action();
- my $error = $self->cust_svc->cust_pkg->$action();
- # $error ||= $self->overlimit($action);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error ${action}ing: $error";
- }
- }
-
- if ($warning_template && &{$op2warncondition{$op}}($self, $column, $amount)) {
- my $wqueue = new FS::queue {
- 'svcnum' => $self->svcnum,
- 'job' => 'FS::svc_acct::reached_threshold',
- };
-
- my $to = '';
- if ($op eq '-'){
- $to = $warning_cc if &{$op2condition{$op}}($self, $column, $amount);
- }
-
- # x_threshold race
- my $error = $wqueue->insert(
- 'svcnum' => $self->svcnum,
- 'op' => $op,
- 'column' => $column,
- 'to' => $to,
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error queuing threshold activity: $error";
- }
- }
-
- warn "$me update successful; committing\n"
- if $DEBUG;
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-sub set_usage {
- my( $self, $valueref ) = @_;
-
- warn "$me set_usage called for svcnum ". $self->svcnum.
- ' ('. $self->email. "): ".
- join(', ', map { "$_ => " . $valueref->{$_}} keys %$valueref) . "\n"
- if $DEBUG;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- local $FS::svc_Common::noexport_hack = 1;
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $reset = 0;
- my %handyhash = ();
- foreach my $field (keys %$valueref){
- $reset = 1 if $valueref->{$field};
- $self->setfield($field, $valueref->{$field});
- $self->setfield( $field.'_threshold',
- int($self->getfield($field)
- * ( $conf->exists('svc_acct-usage_threshold')
- ? 1 - $conf->config('svc_acct-usage_threshold')/100
- : 0.20
- )
- )
- );
- $handyhash{$field} = $self->getfield($field);
- $handyhash{$field.'_threshold'} = $self->getfield($field.'_threshold');
- }
- #my $error = $self->replace; #NO! we avoid the call to ->check for
- #die $error if $error; #services not explicity changed via the UI
-
- my $sql = "UPDATE svc_acct SET " .
- join (',', map { "$_ = ?" } (keys %handyhash) ).
- " WHERE svcnum = ?";
-
- warn "$me $sql\n"
- if $DEBUG;
-
- if (scalar(keys %handyhash)) {
- my $sth = $dbh->prepare( $sql )
- or die "Error preparing $sql: ". $dbh->errstr;
- my $rv = $sth->execute((values %handyhash), $self->svcnum);
- die "Error executing $sql: ". $sth->errstr
- unless defined($rv);
- die "Can't update usage for svcnum ". $self->svcnum
- if $rv == 0;
- }
-
- if ( $reset ) {
- my $error;
-
- if ($self->overlimit) {
- $error = $self->overlimit('unsuspend');
- foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
- if ($part_export->option('overlimit_groups')) {
- my $old = new FS::svc_acct $self->hashref;
- my $groups = &{ $self->_fieldhandlers->{'usergroup'} }
- ($self, $part_export->option('overlimit_groups'));
- $old->usergroup( $groups );
- $error ||= $part_export->export_replace($self, $old);
- }
- }
- }
-
- if ( $conf->exists("svc_acct-usage_unsuspend")) {
- $error ||= $self->cust_svc->cust_pkg->unsuspend;
- }
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error unsuspending: $error";
- }
- }
-
- warn "$me update successful; committing\n"
- if $DEBUG;
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-
-}
-
-
-=item recharge HASHREF
-
- Increments usage columns by the amount specified in HASHREF as
- column=>amount pairs.
-
-=cut
-
-sub recharge {
- my ($self, $vhash) = @_;
-
- if ( $DEBUG ) {
- warn "[$me] recharge called on $self: ". Dumper($self).
- "\nwith vhash: ". Dumper($vhash);
- }
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
- my $error = '';
-
- foreach my $column (keys %$vhash){
- $error ||= $self->_op_usage('+', $column, $vhash->{$column});
- }
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- }else{
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- }
- return $error;
-}
-
-=item is_rechargeable
-
-Returns true if this svc_account can be "recharged" and false otherwise.
-
-=cut
-
-sub is_rechargable {
- my $self = shift;
- $self->seconds ne ''
- || $self->upbytes ne ''
- || $self->downbytes ne ''
- || $self->totalbytes ne '';
-}
-
-=item seconds_since TIMESTAMP
-
-Returns the number of seconds this account has been online since TIMESTAMP,
-according to the session monitor (see L<FS::Session>).
-
-TIMESTAMP is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
-L<Time::Local> and L<Date::Parse> for conversion functions.
-
-=cut
-
-#note: POD here, implementation in FS::cust_svc
-sub seconds_since {
- my $self = shift;
- $self->cust_svc->seconds_since(@_);
-}
-
-=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
-
-Returns the numbers of seconds this account has been online between
-TIMESTAMP_START (inclusive) and TIMESTAMP_END (exclusive), according to an
-external SQL radacct table, specified via sqlradius export. Sessions which
-started in the specified range but are still open are counted from session
-start to the end of the range (unless they are over 1 day old, in which case
-they are presumed missing their stop record and not counted). Also, sessions
-which end in the range but started earlier are counted from the start of the
-range to session end. Finally, sessions which start before the range but end
-after are counted for the entire range.
-
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-=cut
-
-#note: POD here, implementation in FS::cust_svc
-sub seconds_since_sqlradacct {
- my $self = shift;
- $self->cust_svc->seconds_since_sqlradacct(@_);
-}
-
-=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
-
-Returns the sum of the given attribute for all accounts (see L<FS::svc_acct>)
-in this package for sessions ending between TIMESTAMP_START (inclusive) and
-TIMESTAMP_END (exclusive).
-
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-=cut
-
-#note: POD here, implementation in FS::cust_svc
-sub attribute_since_sqlradacct {
- my $self = shift;
- $self->cust_svc->attribute_since_sqlradacct(@_);
-}
-
-=item get_session_history TIMESTAMP_START TIMESTAMP_END
-
-Returns an array of hash references of this customers login history for the
-given time range. (document this better)
-
-=cut
-
-sub get_session_history {
- my $self = shift;
- $self->cust_svc->get_session_history(@_);
-}
-
-=item last_login_text
-
-Returns text describing the time of last login.
-
-=cut
-
-sub last_login_text {
- my $self = shift;
- $self->last_login ? ctime($self->last_login) : 'unknown';
-}
-
-=item get_cdrs TIMESTAMP_START TIMESTAMP_END [ 'OPTION' => 'VALUE ... ]
-
-=cut
-
-sub get_cdrs {
- my($self, $start, $end, %opt ) = @_;
-
- my $did = $self->username; #yup
-
- my $prefix = $opt{'default_prefix'}; #convergent.au '+61'
-
- my $for_update = $opt{'for_update'} ? 'FOR UPDATE' : '';
-
- #SELECT $for_update * FROM cdr
- # WHERE calldate >= $start #need a conversion
- # AND calldate < $end #ditto
- # AND ( charged_party = "$did"
- # OR charged_party = "$prefix$did" #if length($prefix);
- # OR ( ( charged_party IS NULL OR charged_party = '' )
- # AND
- # ( src = "$did" OR src = "$prefix$did" ) # if length($prefix)
- # )
- # )
- # AND ( freesidestatus IS NULL OR freesidestatus = '' )
-
- my $charged_or_src;
- if ( length($prefix) ) {
- $charged_or_src =
- " AND ( charged_party = '$did'
- OR charged_party = '$prefix$did'
- OR ( ( charged_party IS NULL OR charged_party = '' )
- AND
- ( src = '$did' OR src = '$prefix$did' )
- )
- )
- ";
- } else {
- $charged_or_src =
- " AND ( charged_party = '$did'
- OR ( ( charged_party IS NULL OR charged_party = '' )
- AND
- src = '$did'
- )
- )
- ";
-
- }
-
- qsearch(
- 'select' => "$for_update *",
- 'table' => 'cdr',
- 'hashref' => {
- #( freesidestatus IS NULL OR freesidestatus = '' )
- 'freesidestatus' => '',
- },
- 'extra_sql' => $charged_or_src,
-
- );
-
-}
-
-=item radius_groups
-
-Returns all RADIUS groups for this account (see L<FS::radius_usergroup>).
-
-=cut
-
-sub radius_groups {
- my $self = shift;
- if ( $self->usergroup ) {
- confess "explicitly specified usergroup not an arrayref: ". $self->usergroup
- unless ref($self->usergroup) eq 'ARRAY';
- #when provisioning records, export callback runs in svc_Common.pm before
- #radius_usergroup records can be inserted...
- @{$self->usergroup};
- } else {
- map { $_->groupname }
- qsearch('radius_usergroup', { 'svcnum' => $self->svcnum } );
- }
-}
-
-=item clone_suspended
-
-Constructor used by FS::part_export::_export_suspend fallback. Document
-better.
-
-=cut
-
-sub clone_suspended {
- my $self = shift;
- my %hash = $self->hash;
- $hash{_password} = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
- new FS::svc_acct \%hash;
-}
-
-=item clone_kludge_unsuspend
-
-Constructor used by FS::part_export::_export_unsuspend fallback. Document
-better.
-
-=cut
-
-sub clone_kludge_unsuspend {
- my $self = shift;
- my %hash = $self->hash;
- $hash{_password} = '';
- new FS::svc_acct \%hash;
-}
-
-=item check_password
-
-Checks the supplied password against the (possibly encrypted) password in the
-database. Returns true for a successful authentication, false for no match.
-
-Currently supported encryptions are: classic DES crypt() and MD5
-
-=cut
-
-sub check_password {
- my($self, $check_password) = @_;
-
- #remove old-style SUSPENDED kludge, they should be allowed to login to
- #self-service and pay up
- ( my $password = $self->_password ) =~ s/^\*SUSPENDED\* //;
-
- if ( $self->_password_encoding eq 'ldap' ) {
-
- my $auth = from_rfc2307 Authen::Passphrase $self->_password;
- return $auth->match($check_password);
-
- } elsif ( $self->_password_encoding eq 'crypt' ) {
-
- my $auth = from_crypt Authen::Passphrase $self->_password;
- return $auth->match($check_password);
-
- } elsif ( $self->_password_encoding eq 'plain' ) {
-
- return $check_password eq $password;
-
- } else {
-
- #XXX this could be replaced with Authen::Passphrase stuff
-
- if ( $password =~ /^(\*|!!?)$/ ) { #no self-service login
- return 0;
- } elsif ( length($password) < 13 ) { #plaintext
- $check_password eq $password;
- } elsif ( length($password) == 13 ) { #traditional DES crypt
- crypt($check_password, $password) eq $password;
- } elsif ( $password =~ /^\$1\$/ ) { #MD5 crypt
- unix_md5_crypt($check_password, $password) eq $password;
- } elsif ( $password =~ /^\$2a?\$/ ) { #Blowfish
- warn "Can't check password: Blowfish encryption not yet supported, ".
- "svcnum ". $self->svcnum. "\n";
- 0;
- } else {
- warn "Can't check password: Unrecognized encryption for svcnum ".
- $self->svcnum. "\n";
- 0;
- }
-
- }
-
-}
-
-=item crypt_password [ DEFAULT_ENCRYPTION_TYPE ]
-
-Returns an encrypted password, either by passing through an encrypted password
-in the database or by encrypting a plaintext password from the database.
-
-The optional DEFAULT_ENCRYPTION_TYPE parameter can be set to I<crypt> (classic
-UNIX DES crypt), I<md5> (md5 crypt supported by most modern Linux and BSD
-distrubtions), or (eventually) I<blowfish> (blowfish hashing supported by
-OpenBSD, SuSE, other Linux distibutions with pam_unix2, etc.). The default
-encryption type is only used if the password is not already encrypted in the
-database.
-
-=cut
-
-sub crypt_password {
- my $self = shift;
-
- if ( $self->_password_encoding eq 'ldap' ) {
-
- if ( $self->_password =~ /^\{(PLAIN|CLEARTEXT)\}(.+)$/ ) {
- my $plain = $2;
-
- #XXX this could be replaced with Authen::Passphrase stuff
-
- my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
- if ( $encryption eq 'crypt' ) {
- crypt(
- $self->_password,
- $saltset[int(rand(64))].$saltset[int(rand(64))]
- );
- } elsif ( $encryption eq 'md5' ) {
- unix_md5_crypt( $self->_password );
- } elsif ( $encryption eq 'blowfish' ) {
- croak "unknown encryption method $encryption";
- } else {
- croak "unknown encryption method $encryption";
- }
-
- } elsif ( $self->_password =~ /^\{CRYPT\}(.+)$/ ) {
- $1;
- }
-
- } elsif ( $self->_password_encoding eq 'crypt' ) {
-
- return $self->_password;
-
- } elsif ( $self->_password_encoding eq 'plain' ) {
-
- #XXX this could be replaced with Authen::Passphrase stuff
-
- my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
- if ( $encryption eq 'crypt' ) {
- crypt(
- $self->_password,
- $saltset[int(rand(64))].$saltset[int(rand(64))]
- );
- } elsif ( $encryption eq 'md5' ) {
- unix_md5_crypt( $self->_password );
- } elsif ( $encryption eq 'blowfish' ) {
- croak "unknown encryption method $encryption";
- } else {
- croak "unknown encryption method $encryption";
- }
-
- } else {
-
- if ( length($self->_password) == 13
- || $self->_password =~ /^\$(1|2a?)\$/
- || $self->_password =~ /^(\*|NP|\*LK\*|!!?)$/
- )
- {
- $self->_password;
- } else {
-
- #XXX this could be replaced with Authen::Passphrase stuff
-
- my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
- if ( $encryption eq 'crypt' ) {
- crypt(
- $self->_password,
- $saltset[int(rand(64))].$saltset[int(rand(64))]
- );
- } elsif ( $encryption eq 'md5' ) {
- unix_md5_crypt( $self->_password );
- } elsif ( $encryption eq 'blowfish' ) {
- croak "unknown encryption method $encryption";
- } else {
- croak "unknown encryption method $encryption";
- }
-
- }
-
- }
-
-}
-
-=item ldap_password [ DEFAULT_ENCRYPTION_TYPE ]
-
-Returns an encrypted password in "LDAP" format, with a curly-bracked prefix
-describing the format, for example, "{PLAIN}himom", "{CRYPT}94pAVyK/4oIBk" or
-"{MD5}5426824942db4253f87a1009fd5d2d4".
-
-The optional DEFAULT_ENCRYPTION_TYPE is not yet used, but the idea is for it
-to work the same as the B</crypt_password> method.
-
-=cut
-
-sub ldap_password {
- my $self = shift;
- #eventually should check a "password-encoding" field
-
- if ( $self->_password_encoding eq 'ldap' ) {
-
- return $self->_password;
-
- } elsif ( $self->_password_encoding eq 'crypt' ) {
-
- if ( length($self->_password) == 13 ) { #crypt
- return '{CRYPT}'. $self->_password;
- } elsif ( $self->_password =~ /^\$1\$(.*)$/ && length($1) == 31 ) { #passwdMD5
- return '{MD5}'. $1;
- #} elsif ( $self->_password =~ /^\$2a?\$(.*)$/ ) { #Blowfish
- # die "Blowfish encryption not supported in this context, svcnum ".
- # $self->svcnum. "\n";
- } else {
- warn "encryption method not (yet?) supported in LDAP context";
- return '{CRYPT}*'; #unsupported, should not auth
- }
-
- } elsif ( $self->_password_encoding eq 'plain' ) {
-
- return '{PLAIN}'. $self->_password;
-
- #return '{CLEARTEXT}'. $self->_password; #?
-
- } else {
-
- if ( length($self->_password) == 13 ) { #crypt
- return '{CRYPT}'. $self->_password;
- } elsif ( $self->_password =~ /^\$1\$(.*)$/ && length($1) == 31 ) { #passwdMD5
- return '{MD5}'. $1;
- } elsif ( $self->_password =~ /^\$2a?\$(.*)$/ ) { #Blowfish
- warn "Blowfish encryption not supported in this context, svcnum ".
- $self->svcnum. "\n";
- return '{CRYPT}*';
-
- #are these two necessary anymore?
- } elsif ( $self->_password =~ /^(\w{48})$/ ) { #LDAP SSHA
- return '{SSHA}'. $1;
- } elsif ( $self->_password =~ /^(\w{64})$/ ) { #LDAP NS-MTA-MD5
- return '{NS-MTA-MD5}'. $1;
-
- } else { #plaintext
- return '{PLAIN}'. $self->_password;
-
- #return '{CLEARTEXT}'. $self->_password; #?
-
- #XXX this could be replaced with Authen::Passphrase stuff if it gets used
- #my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
- #if ( $encryption eq 'crypt' ) {
- # return '{CRYPT}'. crypt(
- # $self->_password,
- # $saltset[int(rand(64))].$saltset[int(rand(64))]
- # );
- #} elsif ( $encryption eq 'md5' ) {
- # unix_md5_crypt( $self->_password );
- #} elsif ( $encryption eq 'blowfish' ) {
- # croak "unknown encryption method $encryption";
- #} else {
- # croak "unknown encryption method $encryption";
- #}
- }
-
- }
-
-}
-
-=item domain_slash_username
-
-Returns $domain/$username/
-
-=cut
-
-sub domain_slash_username {
- my $self = shift;
- $self->domain. '/'. $self->username. '/';
-}
-
-=item virtual_maildir
-
-Returns $domain/maildirs/$username/
-
-=cut
-
-sub virtual_maildir {
- my $self = shift;
- $self->domain. '/maildirs/'. $self->username. '/';
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item send_email
-
-This is the FS::svc_acct job-queue-able version. It still uses
-FS::Misc::send_email under-the-hood.
-
-=cut
-
-sub send_email {
- my %opt = @_;
-
- eval "use FS::Misc qw(send_email)";
- die $@ if $@;
-
- $opt{mimetype} ||= 'text/plain';
- $opt{mimetype} .= '; charset="iso-8859-1"' unless $opt{mimetype} =~ /charset/;
-
- my $error = send_email(
- 'from' => $opt{from},
- 'to' => $opt{to},
- 'subject' => $opt{subject},
- 'content-type' => $opt{mimetype},
- 'body' => [ map "$_\n", split("\n", $opt{body}) ],
- );
- die $error if $error;
-}
-
-=item check_and_rebuild_fuzzyfiles
-
-=cut
-
-sub check_and_rebuild_fuzzyfiles {
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- -e "$dir/svc_acct.username"
- or &rebuild_fuzzyfiles;
-}
-
-=item rebuild_fuzzyfiles
-
-=cut
-
-sub rebuild_fuzzyfiles {
-
- use Fcntl qw(:flock);
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
-
- #username
-
- open(USERNAMELOCK,">>$dir/svc_acct.username")
- or die "can't open $dir/svc_acct.username: $!";
- flock(USERNAMELOCK,LOCK_EX)
- or die "can't lock $dir/svc_acct.username: $!";
-
- my @all_username = map $_->getfield('username'), qsearch('svc_acct', {});
-
- open (USERNAMECACHE,">$dir/svc_acct.username.tmp")
- or die "can't open $dir/svc_acct.username.tmp: $!";
- print USERNAMECACHE join("\n", @all_username), "\n";
- close USERNAMECACHE or die "can't close $dir/svc_acct.username.tmp: $!";
-
- rename "$dir/svc_acct.username.tmp", "$dir/svc_acct.username";
- close USERNAMELOCK;
-
-}
-
-=item all_username
-
-=cut
-
-sub all_username {
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- open(USERNAMECACHE,"<$dir/svc_acct.username")
- or die "can't open $dir/svc_acct.username: $!";
- my @array = map { chomp; $_; } <USERNAMECACHE>;
- close USERNAMECACHE;
- \@array;
-}
-
-=item append_fuzzyfiles USERNAME
-
-=cut
-
-sub append_fuzzyfiles {
- my $username = shift;
-
- &check_and_rebuild_fuzzyfiles;
-
- use Fcntl qw(:flock);
-
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
-
- open(USERNAME,">>$dir/svc_acct.username")
- or die "can't open $dir/svc_acct.username: $!";
- flock(USERNAME,LOCK_EX)
- or die "can't lock $dir/svc_acct.username: $!";
-
- print USERNAME "$username\n";
-
- flock(USERNAME,LOCK_UN)
- or die "can't unlock $dir/svc_acct.username: $!";
- close USERNAME;
-
- 1;
-}
-
-
-
-=item radius_usergroup_selector GROUPS_ARRAYREF [ SELECTNAME ]
-
-=cut
-
-sub radius_usergroup_selector {
- my $sel_groups = shift;
- my %sel_groups = map { $_=>1 } @$sel_groups;
-
- my $selectname = shift || 'radius_usergroup';
-
- my $dbh = dbh;
- my $sth = $dbh->prepare(
- 'SELECT DISTINCT(groupname) FROM radius_usergroup ORDER BY groupname'
- ) or die $dbh->errstr;
- $sth->execute() or die $sth->errstr;
- my @all_groups = map { $_->[0] } @{$sth->fetchall_arrayref};
-
- my $html = <<END;
- <SCRIPT>
- function ${selectname}_doadd(object) {
- var myvalue = object.${selectname}_add.value;
- var optionName = new Option(myvalue,myvalue,false,true);
- var length = object.$selectname.length;
- object.$selectname.options[length] = optionName;
- object.${selectname}_add.value = "";
- }
- </SCRIPT>
- <SELECT MULTIPLE NAME="$selectname">
-END
-
- foreach my $group ( @all_groups ) {
- $html .= qq(<OPTION VALUE="$group");
- if ( $sel_groups{$group} ) {
- $html .= ' SELECTED';
- $sel_groups{$group} = 0;
- }
- $html .= ">$group</OPTION>\n";
- }
- foreach my $group ( grep { $sel_groups{$_} } keys %sel_groups ) {
- $html .= qq(<OPTION VALUE="$group" SELECTED>$group</OPTION>\n);
- };
- $html .= '</SELECT>';
-
- $html .= qq!<BR><INPUT TYPE="text" NAME="${selectname}_add">!.
- qq!<INPUT TYPE="button" VALUE="Add new group" onClick="${selectname}_doadd(this.form)">!;
-
- $html;
-}
-
-=item reached_threshold
-
-Performs some activities when svc_acct thresholds (such as number of seconds
-remaining) are reached.
-
-=cut
-
-sub reached_threshold {
- my %opt = @_;
-
- my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $opt{'svcnum'} } );
- die "Cannot find svc_acct with svcnum " . $opt{'svcnum'} unless $svc_acct;
-
- if ( $opt{'op'} eq '+' ){
- $svc_acct->setfield( $opt{'column'}.'_threshold',
- int($svc_acct->getfield($opt{'column'})
- * ( $conf->exists('svc_acct-usage_threshold')
- ? $conf->config('svc_acct-usage_threshold')/100
- : 0.80
- )
- )
- );
- my $error = $svc_acct->replace;
- die $error if $error;
- }elsif ( $opt{'op'} eq '-' ){
-
- my $threshold = $svc_acct->getfield( $opt{'column'}.'_threshold' );
- return '' if ($threshold eq '' );
-
- $svc_acct->setfield( $opt{'column'}.'_threshold', 0 );
- my $error = $svc_acct->replace;
- die $error if $error; # email next time, i guess
-
- if ( $warning_template ) {
- eval "use FS::Misc qw(send_email)";
- die $@ if $@;
-
- my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
- my $cust_main = $cust_pkg->cust_main;
-
- my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ }
- $cust_main->invoicing_list,
- ($opt{'to'} ? $opt{'to'} : ())
- );
-
- my $mimetype = $warning_mimetype;
- $mimetype .= '; charset="iso-8859-1"' unless $opt{mimetype} =~ /charset/;
-
- my $body = $warning_template->fill_in( HASH => {
- 'custnum' => $cust_main->custnum,
- 'username' => $svc_acct->username,
- 'password' => $svc_acct->_password,
- 'first' => $cust_main->first,
- 'last' => $cust_main->getfield('last'),
- 'pkg' => $cust_pkg->part_pkg->pkg,
- 'column' => $opt{'column'},
- 'amount' => $opt{'column'} =~/bytes/
- ? FS::UI::bytecount::display_bytecount($svc_acct->getfield($opt{'column'}))
- : $svc_acct->getfield($opt{'column'}),
- 'threshold' => $opt{'column'} =~/bytes/
- ? FS::UI::bytecount::display_bytecount($threshold)
- : $threshold,
- } );
-
-
- my $error = send_email(
- 'from' => $warning_from,
- 'to' => $to,
- 'subject' => $warning_subject,
- 'content-type' => $mimetype,
- 'body' => [ map "$_\n", split("\n", $body) ],
- );
- die $error if $error;
- }
- }else{
- die "unknown op: " . $opt{'op'};
- }
-}
-
-=back
-
-=head1 BUGS
-
-The $recref stuff in sub check should be cleaned up.
-
-The suspend, unsuspend and cancel methods update the database, but not the
-current object. This is probably a bug as it's unexpected and
-counterintuitive.
-
-radius_usergroup_selector? putting web ui components in here? they should
-probably live somewhere else...
-
-insertion of RADIUS group stuff in insert could be done with child_objects now
-(would probably clean up export of them too)
-
-=head1 SEE ALSO
-
-L<FS::svc_Common>, edit/part_svc.cgi from an installed web interface,
-export.html from the base documentation, L<FS::Record>, L<FS::Conf>,
-L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, L<FS::queue>,
-L<freeside-queued>), L<FS::svc_acct_pop>,
-schema.html from the base documentation.
-
-=cut
-
-=item domain_select_hash %OPTIONS
-
-Returns a hash SVCNUM => DOMAIN ... representing the domains this customer
-may at present purchase.
-
-Currently available options are: I<pkgnum> I<svcpart>
-
-=cut
-
-sub domain_select_hash {
- my ($self, %options) = @_;
- my %domains = ();
- my $part_svc;
- my $cust_pkg;
-
- if (ref($self)) {
- $part_svc = $self->part_svc;
- $cust_pkg = $self->cust_svc->cust_pkg
- if $self->cust_svc;
- }
-
- $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} })
- if $options{'svcpart'};
-
- $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} })
- if $options{'pkgnum'};
-
- if ($part_svc && ( $part_svc->part_svc_column('domsvc')->columnflag eq 'S'
- || $part_svc->part_svc_column('domsvc')->columnflag eq 'F')) {
- %domains = map { $_->svcnum => $_->domain }
- map { qsearchs('svc_domain', { 'svcnum' => $_ }) }
- split(',', $part_svc->part_svc_column('domsvc')->columnvalue);
- }elsif ($cust_pkg && !$conf->exists('svc_acct-alldomains') ) {
- %domains = map { $_->svcnum => $_->domain }
- map { qsearchs('svc_domain', { 'svcnum' => $_->svcnum }) }
- map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
- qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum });
- }else{
- %domains = map { $_->svcnum => $_->domain } qsearch('svc_domain', {} );
- }
-
- if ($part_svc && $part_svc->part_svc_column('domsvc')->columnflag eq 'D') {
- my $svc_domain = qsearchs('svc_domain',
- { 'svcnum' => $part_svc->part_svc_column('domsvc')->columnvalue } );
- if ( $svc_domain ) {
- $domains{$svc_domain->svcnum} = $svc_domain->domain;
- }else{
- warn "unknown svc_domain.svcnum for part_svc_column domsvc: ".
- $part_svc->part_svc_column('domsvc')->columnvalue;
-
- }
- }
-
- (%domains);
-}
-
-1;
-
diff --git a/FS/FS/svc_acct_pop.pm b/FS/FS/svc_acct_pop.pm
deleted file mode 100644
index de41f5b..0000000
--- a/FS/FS/svc_acct_pop.pm
+++ /dev/null
@@ -1,206 +0,0 @@
-package FS::svc_acct_pop;
-
-use strict;
-use vars qw( @ISA @EXPORT_OK @svc_acct_pop %svc_acct_pop );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw( FS::Record Exporter );
-@EXPORT_OK = qw( popselector );
-
-=head1 NAME
-
-FS::svc_acct_pop - Object methods for svc_acct_pop records
-
-=head1 SYNOPSIS
-
- use FS::svc_acct_pop;
-
- $record = new FS::svc_acct_pop \%hash;
- $record = new FS::svc_acct_pop { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $html = FS::svc_acct_pop::popselector( $popnum, $state );
-
-=head1 DESCRIPTION
-
-An FS::svc_acct object represents an point of presence. FS::svc_acct_pop
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item popnum - primary key (assigned automatically for new accounts)
-
-=item city
-
-=item state
-
-=item ac - area code
-
-=item exch - exchange
-
-=item loc - rest of number
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new point of presence (if only it were that easy!). To add the
-point of presence to the database, see L<"insert">.
-
-=cut
-
-sub table { 'svc_acct_pop'; }
-
-=item insert
-
-Adds this point of presence to the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item delete
-
-Removes this point of presence from the database.
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid point of presence. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- $self->ut_numbern('popnum')
- or $self->ut_text('city')
- or $self->ut_text('state')
- or $self->ut_number('ac')
- or $self->ut_number('exch')
- or $self->ut_numbern('loc')
- or $self->SUPER::check
- ;
-
-}
-
-=item text
-
-Returns:
-
-"$city, $state ($ac)/$exch"
-
-=cut
-
-sub text {
- my $self = shift;
- $self->city. ', '. $self->state.
- ' ('. $self->ac. ')/'. $self->exch. '-'. $self->loc;
-}
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item popselector [ POPNUM [ STATE ] ]
-
-=cut
-
-#horrible false laziness with signup.cgi (pull special-case for 0 & 1
-# pop code out from signup.cgi??)
-sub popselector {
- my( $popnum, $state ) = @_;
-
- unless ( @svc_acct_pop ) { #cache pop list
- @svc_acct_pop = qsearch('svc_acct_pop', {} );
- %svc_acct_pop = ();
- push @{$svc_acct_pop{$_->state}}, $_ foreach @svc_acct_pop;
- }
-
- my $text = <<END;
- <SCRIPT>
- function opt(what,href,text) {
- var optionName = new Option(text, href, false, false)
- var length = what.length;
- what.options[length] = optionName;
- }
-
- function popstate_changed(what) {
- state = what.options[what.selectedIndex].text;
- what.form.popnum.options.length = 0
- what.form.popnum.options[0] = new Option("", "", false, true);
-END
-
- foreach my $popstate ( sort { $a cmp $b } keys %svc_acct_pop ) {
- $text .= "\nif ( state == \"$popstate\" ) {\n";
-
- foreach my $pop ( @{$svc_acct_pop{$popstate}}) {
- my $o_popnum = $pop->popnum;
- my $poptext = $pop->text;
- $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n"
- }
- $text .= "}\n";
- }
-
- $text .= "}\n</SCRIPT>\n";
-
- $text .=
- qq!<SELECT NAME="popstate" SIZE=1 onChange="popstate_changed(this)">!.
- qq!<OPTION> !;
- $text .= "<OPTION>$_" foreach sort { $a cmp $b } keys %svc_acct_pop;
- $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD><TD>';
-
- $text .= qq!<SELECT NAME="popnum" SIZE=1><OPTION> !;
- my @initial_select;
- if ( scalar(@svc_acct_pop) > 100 ) {
- @initial_select = qsearchs( 'svc_acct_pop', { 'popnum' => $popnum } );
- } else {
- @initial_select = @svc_acct_pop;
- }
- foreach my $pop ( @initial_select ) {
- $text .= qq!<OPTION VALUE="!. $pop->popnum. '"'.
- ( ( $popnum && $pop->popnum == $popnum ) ? ' SELECTED' : '' ). ">".
- $pop->text;
- }
- $text .= '</SELECT>';
-
- $text;
-
-}
-
-=back
-
-=head1 BUGS
-
-It should be renamed to part_pop.
-
-popselector? putting web ui components in here? they should probably live
-somewhere else...
-
-popselector: pull special-case for 0 & 1 pop code out from signup.cgi
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::svc_acct>, L<FS::part_pop_local>, schema.html from the
-base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm
deleted file mode 100755
index d123524..0000000
--- a/FS/FS/svc_broadband.pm
+++ /dev/null
@@ -1,301 +0,0 @@
-package FS::svc_broadband;
-
-use strict;
-use vars qw(@ISA $conf);
-use FS::Record qw( qsearchs qsearch dbh );
-use FS::svc_Common;
-use FS::cust_svc;
-use FS::addr_block;
-use NetAddr::IP;
-
-@ISA = qw( FS::svc_Common );
-
-$FS::UID::callback{'FS::svc_broadband'} = sub {
- $conf = new FS::Conf;
-};
-
-=head1 NAME
-
-FS::svc_broadband - Object methods for svc_broadband records
-
-=head1 SYNOPSIS
-
- use FS::svc_broadband;
-
- $record = new FS::svc_broadband \%hash;
- $record = new FS::svc_broadband { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $error = $record->cancel;
-
-=head1 DESCRIPTION
-
-An FS::svc_broadband object represents a 'broadband' Internet connection, such
-as a DSL, cable modem, or fixed wireless link. These services are assumed to
-have the following properties:
-
-FS::svc_broadband inherits from FS::svc_Common. The following fields are
-currently supported:
-
-=over 4
-
-=item svcnum - primary key
-
-=item blocknum - see FS::addr_block
-
-=item
-speed_up - maximum upload speed, in bits per second. If set to zero, upload
-speed will be unlimited. Exports that do traffic shaping should handle this
-correctly, and not blindly set the upload speed to zero and kill the customer's
-connection.
-
-=item
-speed_down - maximum download speed, as above
-
-=item ip_addr - the customer's IP address. If the customer needs more than one
-IP address, set this to the address of the customer's router. As a result, the
-customer's router will have the same address for both its internal and external
-interfaces thus saving address space. This has been found to work on most NAT
-routers available.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new svc_broadband. To add the record to the database, see
-"insert".
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table_info {
- {
- 'name' => 'Broadband',
- 'name_plural' => 'Broadband services',
- 'longname_plural' => 'Fixed (username-less) broadband services',
- 'display_weight' => 50,
- 'cancel_weight' => 70,
- 'fields' => {
- 'description' => 'Descriptive label for this particular device.',
- 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.',
- 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.',
- 'ip_addr' => 'IP address. Leave blank for automatic assignment.',
- 'blocknum' => { 'label' => 'Address block',
- 'type' => 'select',
- 'select_table' => 'addr_block',
- 'select_key' => 'blocknum',
- 'select_label' => 'cidr',
- 'disable_inventory' => 1,
- },
- },
- };
-}
-
-sub table { 'svc_broadband'; }
-
-=item search_sql STRING
-
-Class method which returns an SQL fragment to search for the given string.
-
-=cut
-
-sub search_sql {
- my( $class, $string ) = @_;
- if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
- $class->search_sql_field('ip_addr', $string );
- }elsif ( $string =~ /^([a-fA-F0-9]{12})$/ ) {
- $class->search_sql_field('mac_addr', uc($string));
- }elsif ( $string =~ /^(([a-fA-F0-9]{1,2}:){5}([a-fA-F0-9]{1,2}))$/ ) {
- $class->search_sql_field('mac_addr', uc("$2$3$4$5$6$7") );
- } else {
- '1 = 0'; #false
- }
-}
-
-=item label
-
-Returns the IP address.
-
-=cut
-
-sub label {
- my $self = shift;
- $self->ip_addr;
-}
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-The additional fields pkgnum and svcpart (see FS::cust_svc) should be
-defined. An FS::cust_svc record will be created and inserted.
-
-Currently available options are: I<depend_jobnum>
-
-If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
-jobnums), all provisioning jobs will have a dependancy on the supplied
-jobnum(s) (they will not run until the specific job(s) complete(s)).
-
-=cut
-
-# Standard FS::svc_Common::insert
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# Standard FS::svc_Common::delete
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# Standard FS::svc_Common::replace
-
-=item suspend
-
-Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
-
-=item unsuspend
-
-Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg).
-
-=item cancel
-
-Called by the cancel method of FS::cust_pkg (see FS::cust_pkg).
-
-=item check
-
-Checks all fields to make sure this is a valid broadband service. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
- my $x = $self->setfixed;
-
- return $x unless ref($x);
-
- my $error =
- $self->ut_numbern('svcnum')
- || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum')
- || $self->ut_textn('description')
- || $self->ut_number('speed_up')
- || $self->ut_number('speed_down')
- || $self->ut_ipn('ip_addr')
- || $self->ut_hexn('mac_addr')
- || $self->ut_hexn('auth_key')
- || $self->ut_coordn('latitude', -90, 90)
- || $self->ut_coordn('longitude', -180, 180)
- || $self->ut_sfloatn('altitude')
- || $self->ut_textn('vlan_profile')
- ;
- return $error if $error;
-
- if($self->speed_up < 0) { return 'speed_up must be positive'; }
- if($self->speed_down < 0) { return 'speed_down must be positive'; }
-
- if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
- my $next_addr = $self->addr_block->next_free_addr;
- if ($next_addr) {
- $self->ip_addr($next_addr->addr);
- } else {
- return "No free addresses in addr_block (blocknum: ".$self->blocknum.")";
- }
- }
-
- # This should catch errors in the ip_addr. If it doesn't,
- # they'll almost certainly not map into the block anyway.
- my $self_addr = $self->NetAddr; #netmask is /32
- return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr;
-
- my $block_addr = $self->addr_block->NetAddr;
- unless ($block_addr->contains($self_addr)) {
- return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr;
- }
-
- my $router = $self->addr_block->router
- or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum;
- if(grep { $_->routernum == $router->routernum} $self->allowed_routers) {
- } # do nothing
- else {
- return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
- }
-
- $self->SUPER::check;
-}
-
-=item NetAddr
-
-Returns a NetAddr::IP object containing the IP address of this service. The netmask
-is /32.
-
-=cut
-
-sub NetAddr {
- my $self = shift;
- new NetAddr::IP ($self->ip_addr);
-}
-
-=item addr_block
-
-Returns the FS::addr_block record (i.e. the address block) for this broadband service.
-
-=cut
-
-sub addr_block {
- my $self = shift;
- qsearchs('addr_block', { blocknum => $self->blocknum });
-}
-
-=back
-
-=item allowed_routers
-
-Returns a list of allowed FS::router objects.
-
-=cut
-
-sub allowed_routers {
- my $self = shift;
- map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart });
-}
-
-=head1 BUGS
-
-The business with sb_field has been 'fixed', in a manner of speaking.
-
-=head1 SEE ALSO
-
-FS::svc_Common, FS::Record, FS::addr_block,
-FS::part_svc, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm
deleted file mode 100644
index 758b399b..0000000
--- a/FS/FS/svc_domain.pm
+++ /dev/null
@@ -1,478 +0,0 @@
-package FS::svc_domain;
-
-use strict;
-use vars qw( @ISA $whois_hack $conf
- @defaultrecords $soadefaultttl $soaemail $soaexpire $soamachine
- $soarefresh $soaretry
-);
-use Carp;
-use Date::Format;
-#use Net::Whois::Raw;
-use Net::Domain::TLD qw(tld_exists);
-use FS::Record qw(fields qsearch qsearchs dbh);
-use FS::Conf;
-use FS::svc_Common;
-use FS::svc_Parent_Mixin;
-use FS::cust_svc;
-use FS::svc_acct;
-use FS::cust_pkg;
-use FS::cust_main;
-use FS::domain_record;
-use FS::queue;
-
-@ISA = qw( FS::svc_Parent_Mixin FS::svc_Common );
-
-#ask FS::UID to run this stuff for us later
-$FS::UID::callback{'FS::domain'} = sub {
- $conf = new FS::Conf;
-
- @defaultrecords = $conf->config('defaultrecords');
- $soadefaultttl = $conf->config('soadefaultttl');
- $soaemail = $conf->config('soaemail');
- $soaexpire = $conf->config('soaexpire');
- $soamachine = $conf->config('soamachine');
- $soarefresh = $conf->config('soarefresh');
- $soaretry = $conf->config('soaretry');
-
-};
-
-=head1 NAME
-
-FS::svc_domain - Object methods for svc_domain records
-
-=head1 SYNOPSIS
-
- use FS::svc_domain;
-
- $record = new FS::svc_domain \%hash;
- $record = new FS::svc_domain { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $error = $record->cancel;
-
-=head1 DESCRIPTION
-
-An FS::svc_domain object represents a domain. FS::svc_domain inherits from
-FS::svc_Common. The following fields are currently supported:
-
-=over 4
-
-=item svcnum - primary key (assigned automatically for new accounts)
-
-=item domain
-
-=item catchall - optional svcnum of an svc_acct record, designating an email catchall account.
-
-=item suffix -
-
-=item parent_svcnum -
-
-=item registrarnum - Registrar (see L<FS::registrar>)
-
-=item registrarkey - Registrar key or password for this domain
-
-=item setup_date - UNIX timestamp
-
-=item renewal_interval - Number of days before expiration date to start renewal
-
-=item expiration_date - UNIX timestamp
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new domain. To add the domain to the database, see L<"insert">.
-
-=cut
-
-sub table_info {
- {
- 'name' => 'Domain',
- 'sorts' => 'domain',
- 'display_weight' => 20,
- 'cancel_weight' => 60,
- 'fields' => {
- 'domain' => 'Domain',
- },
- };
-}
-
-sub table { 'svc_domain'; }
-
-sub search_sql {
- my($class, $string) = @_;
- $class->search_sql_field('domain', $string);
-}
-
-
-=item label
-
-Returns the domain.
-
-=cut
-
-sub label {
- my $self = shift;
- $self->domain;
-}
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this domain to the database. If there is an error, returns the error,
-otherwise returns false.
-
-The additional fields I<pkgnum> and I<svcpart> (see L<FS::cust_svc>) should be
-defined. An FS::cust_svc record will be created and inserted.
-
-The additional field I<action> should be set to I<N> for new domains or I<M>
-for transfers.
-
-A registration or transfer email will be submitted unless
-$FS::svc_domain::whois_hack is true.
-
-The additional field I<email> can be used to manually set the admin contact
-email address on this email. Otherwise, the svc_acct records for this package
-(see L<FS::cust_pkg>) are searched. If there is exactly one svc_acct record
-in the same package, it is automatically used. Otherwise an error is returned.
-
-If any I<soamachine> configuration file exists, an SOA record is added to
-the domain_record table (see <FS::domain_record>).
-
-If any records are defined in the I<defaultrecords> configuration file,
-appropriate records are added to the domain_record table (see
-L<FS::domain_record>).
-
-Currently available options are: I<depend_jobnum>
-
-If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
-jobnums), all provisioning jobs will have a dependancy on the supplied
-jobnum(s) (they will not run until the specific job(s) complete(s)).
-
-=cut
-
-sub insert {
- my $self = shift;
- my $error;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $error = $self->check;
- return $error if $error;
-
- return "Domain in use (here)"
- if qsearchs( 'svc_domain', { 'domain' => $self->domain } );
-
-
- $error = $self->SUPER::insert(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- if ( $soamachine ) {
- my $soa = new FS::domain_record {
- 'svcnum' => $self->svcnum,
- 'reczone' => '@',
- 'recaf' => 'IN',
- 'rectype' => 'SOA',
- 'recdata' => "$soamachine $soaemail ( ". time2str("%Y%m%d", time). "00 ".
- "$soarefresh $soaretry $soaexpire $soadefaultttl )"
- };
- $error = $soa->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "couldn't insert SOA record for new domain: $error";
- }
-
- foreach my $record ( @defaultrecords ) {
- my($zone,$af,$type,$data) = split(/\s+/,$record,4);
- my $domain_record = new FS::domain_record {
- 'svcnum' => $self->svcnum,
- 'reczone' => $zone,
- 'recaf' => $af,
- 'rectype' => $type,
- 'recdata' => $data,
- };
- my $error = $domain_record->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "couldn't insert record for new domain: $error";
- }
- }
-
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
- ''; #no error
-}
-
-=item delete
-
-Deletes this domain from the database. If there is an error, returns the
-error, otherwise returns false.
-
-The corresponding FS::cust_svc record will be deleted as well.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- return "Can't delete a domain which has accounts!"
- if qsearch( 'svc_acct', { 'domsvc' => $self->svcnum } );
-
- #return "Can't delete a domain with (domain_record) zone entries!"
- # if qsearch('domain_record', { 'svcnum' => $self->svcnum } );
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- foreach my $domain_record ( reverse $self->domain_record ) {
- my $error = $domain_record->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't delete DNS entry: ".
- join(' ', map $domain_record->$_(),
- qw( reczone recaf rectype recdata )
- ).
- ":$error";
- }
- }
-
- my $error = $self->SUPER::delete(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-}
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my ( $new, $old ) = ( shift, shift );
-
- # We absolutely have to have an old vs. new record to make this work.
- $old = $new->replace_old unless defined($old);
-
- return "Can't change domain - reorder."
- if $old->getfield('domain') ne $new->getfield('domain');
-
- # Better to do it here than to force the caller to remember that svc_domain is weird.
- $new->setfield(action => 'M');
- my $error = $new->SUPER::replace($old, @_);
- return $error if $error;
-}
-
-=item suspend
-
-Just returns false (no error) for now.
-
-Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item unsuspend
-
-Just returns false (no error) for now.
-
-Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item cancel
-
-Just returns false (no error) for now.
-
-Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item check
-
-Checks all fields to make sure this is a valid domain. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-Sets any fixed values; see L<FS::part_svc>.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $x = $self->setfixed;
- return $x unless ref($x);
- #my $part_svc = $x;
-
- my $error = $self->ut_numbern('svcnum')
- || $self->ut_numbern('catchall')
- ;
- return $error if $error;
-
- #hmm
- my $pkgnum;
- if ( $self->svcnum ) {
- my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } );
- $pkgnum = $cust_svc->pkgnum;
- } else {
- $pkgnum = $self->pkgnum;
- }
-
- my($recref) = $self->hashref;
-
- #if ( $recref->{domain} =~ /^([\w\-\.]{1,22})\.(com|net|org|edu)$/ ) {
- if ( $recref->{domain} =~ /^([\w\-]{1,63})\.(com|net|org|edu|tv|info|biz)$/ ) {
- $recref->{domain} = "$1.$2";
- $recref->{suffix} ||= $2;
- # hmmmmmmmm.
- } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)\.(\w+)$/ ) {
- $recref->{domain} = "$1.$2";
- # need to match a list of suffixes - no guarantee they're top-level..
- # http://wiki.mozilla.org/TLD_List
- # but this will have to do for now...
- $recref->{suffix} ||= $2;
- } else {
- return "Illegal domain ". $recref->{domain}.
- " (or unknown registry - try \$whois_hack)";
- }
-
- $self->suffix =~ /(^|\.)(\w+)$/
- or return "can't parse suffix for TLD: ". $self->suffix;
- my $tld = $2;
- return "No such TLD: .$tld" unless tld_exists($tld);
-
- if ( $recref->{catchall} ne '' ) {
- my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $recref->{catchall} } );
- return "Unknown catchall" unless $svc_acct;
- }
-
- $self->ut_alphan('suffix')
- or $self->ut_foreign_keyn('registrarnum', 'registrar', 'registrarnum')
- or $self->ut_textn('registrarkey')
- or $self->ut_numbern('setup_date')
- or $self->ut_numbern('renewal_interval')
- or $self->ut_numbern('expiration_date')
- or $self->ut_textn('purpose')
- or $self->SUPER::check;
-
-}
-
-=item domain_record
-
-=cut
-
-sub domain_record {
- my $self = shift;
-
- my %order = (
- 'SOA' => 1,
- 'NS' => 2,
- 'MX' => 3,
- 'CNAME' => 4,
- 'A' => 5,
- 'TXT' => 6,
- 'PTR' => 7,
- );
-
- my %sort = (
- #'SOA' => sub { $_[0]->recdata cmp $_[1]->recdata }, #sure hope not though
-# 'SOA' => sub { 0; },
-# 'NS' => sub { 0; },
- 'MX' => sub { my( $a_weight, $a_name ) = split(/\s+/, $_[0]->recdata);
- my( $b_weight, $b_name ) = split(/\s+/, $_[1]->recdata);
- $a_weight <=> $b_weight or $a_name cmp $b_name;
- },
- 'CNAME' => sub { $_[0]->reczone cmp $_[1]->reczone },
- 'A' => sub { $_[0]->reczone cmp $_[1]->reczone },
-
-# 'TXT' => sub { 0; },
- 'PTR' => sub { $_[0]->reczone <=> $_[1]->reczone },
- );
-
- sort { $order{$a->rectype} <=> $order{$b->rectype}
- or &{ $sort{$a->rectype} || sub { 0; } }($a, $b)
- }
- qsearch('domain_record', { svcnum => $self->svcnum } );
-
-}
-
-sub catchall_svc_acct {
- my $self = shift;
- if ( $self->catchall ) {
- qsearchs( 'svc_acct', { 'svcnum' => $self->catchall } );
- } else {
- '';
- }
-}
-
-=item whois
-
-# Returns the Net::Whois::Domain object (see L<Net::Whois>) for this domain, or
-# undef if the domain is not found in whois.
-
-(If $FS::svc_domain::whois_hack is true, returns that in all cases instead.)
-
-=cut
-
-sub whois {
- #$whois_hack or new Net::Whois::Domain $_[0]->domain;
- #$whois_hack or die "whois_hack not set...\n";
-}
-
-=back
-
-=head1 BUGS
-
-Delete doesn't send a registration template.
-
-All registries should be supported.
-
-Should change action to a real field.
-
-The $recref stuff in sub check should be cleaned up.
-
-=head1 SEE ALSO
-
-L<FS::svc_Common>, L<FS::Record>, L<FS::Conf>, L<FS::cust_svc>,
-L<FS::part_svc>, L<FS::cust_pkg>, L<Net::Whois>, schema.html from the base
-documentation, config.html from the base documentation.
-
-=cut
-
-1;
-
-
diff --git a/FS/FS/svc_external.pm b/FS/FS/svc_external.pm
deleted file mode 100644
index 0fb391f..0000000
--- a/FS/FS/svc_external.pm
+++ /dev/null
@@ -1,204 +0,0 @@
-package FS::svc_external;
-
-use strict;
-use vars qw(@ISA);
-use FS::Conf;
-use FS::svc_External_Common;
-
-@ISA = qw( FS::svc_External_Common );
-
-=head1 NAME
-
-FS::svc_external - Object methods for svc_external records
-
-=head1 SYNOPSIS
-
- use FS::svc_external;
-
- $record = new FS::svc_external \%hash;
- $record = new FS::svc_external { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $error = $record->cancel;
-
-=head1 DESCRIPTION
-
-An FS::svc_external object represents a generic externally tracked service.
-FS::svc_external inherits from FS::svc_External_Common (and FS::svc_Common).
-The following fields are currently supported:
-
-=over 4
-
-=item svcnum - primary key
-
-=item id - unique number of external record
-
-=item title - for invoice line items
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new external service. To add the external service to the database,
-see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table_info {
- {
- 'name' => 'External service',
- 'sorts' => 'id',
- 'display_weight' => 90,
- 'cancel_weight' => 10,
- 'fields' => {
- 'id' => { label => 'Unique number of external record',
- type => 'text',
- disable_default => 1,
- disable_fixed => 1,
- },
- 'title' => { label => 'Printed on invoice line items',
- type => 'text',
- disable_inventory => 1,
- },
- },
- };
-}
-
-sub table { 'svc_external'; }
-
-# oh! this should be moved to svc_artera_turbo or something now
-sub label {
- my $self = shift;
- my $conf = new FS::Conf;
- if ( $conf->exists('svc_external-display_type')
- && $conf->config('svc_external-display_type') eq 'artera_turbo' )
- {
- sprintf('%010d', $self->id). '-'.
- substr('0000000000'.uc($self->title), -10);
- } else {
- #$self->SUPER::label;
- $self->id. ' - '. $self->title;
- }
-}
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this external service to the database. If there is an error, returns the
-error, otherwise returns false.
-
-The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
-defined. An FS::cust_svc record will be created and inserted.
-
-Currently available options are: I<depend_jobnum>
-
-If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
-jobnums), all provisioning jobs will have a dependancy on the supplied
-jobnum(s) (they will not run until the specific job(s) complete(s)).
-
-=cut
-
-#sub insert {
-# my $self = shift;
-# my $error;
-#
-# $error = $self->SUPER::insert(@_);
-# return $error if $error;
-#
-# '';
-#}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-#sub delete {
-# my $self = shift;
-# my $error;
-#
-# $error = $self->SUPER::delete;
-# return $error if $error;
-#
-# '';
-#}
-
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-#sub replace {
-# my ( $new, $old ) = ( shift, shift );
-# my $error;
-#
-# $error = $new->SUPER::replace($old);
-# return $error if $error;
-#
-# '';
-#}
-
-=item suspend
-
-Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item unsuspend
-
-Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item cancel
-
-Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item check
-
-Checks all fields to make sure this is a valid external service. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-#sub check {
-# my $self = shift;
-# my $error;
-#
-# $error = $self->SUPER::delete;
-# return $error if $error;
-#
-# '';
-#}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::svc_External_Common>, L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>,
-L<FS::part_svc>, L<FS::cust_pkg>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/svc_forward.pm b/FS/FS/svc_forward.pm
deleted file mode 100644
index 3250f8a..0000000
--- a/FS/FS/svc_forward.pm
+++ /dev/null
@@ -1,371 +0,0 @@
-package FS::svc_forward;
-
-use strict;
-use vars qw( @ISA );
-use FS::Conf;
-use FS::Record qw( fields qsearch qsearchs dbh );
-use FS::svc_Common;
-use FS::cust_svc;
-use FS::svc_acct;
-use FS::svc_domain;
-
-@ISA = qw( FS::svc_Common );
-
-=head1 NAME
-
-FS::svc_forward - Object methods for svc_forward records
-
-=head1 SYNOPSIS
-
- use FS::svc_forward;
-
- $record = new FS::svc_forward \%hash;
- $record = new FS::svc_forward { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $error = $record->cancel;
-
-=head1 DESCRIPTION
-
-An FS::svc_forward object represents a mail forwarding alias. FS::svc_forward
-inherits from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item svcnum - primary key (assigned automatcially for new accounts)
-
-=item srcsvc - svcnum of the source of the forward (see L<FS::svc_acct>)
-
-=item src - literal source (username or full email address)
-
-=item dstsvc - svcnum of the destination of the forward (see L<FS::svc_acct>)
-
-=item dst - literal destination (username or full email address)
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new mail forwarding alias. To add the mail forwarding alias to the
-database, see L<"insert">.
-
-=cut
-
-
-sub table_info {
- {
- 'name' => 'Forward',
- 'name_plural' => 'Mail forwards',
- 'display_weight' => 30,
- 'cancel_weight' => 30,
- 'fields' => {
- 'srcsvc' => 'service from which mail is to be forwarded',
- 'dstsvc' => 'service to which mail is to be forwarded',
- 'dst' => 'someone@another.domain.com to use when dstsvc is 0',
- },
- };
-}
-
-sub table { 'svc_forward'; }
-
-=item search_sql STRING
-
-Class method which returns an SQL fragment to search for the given string.
-
-=cut
-
-sub search_sql {
- my( $class, $string ) = @_;
- $class->search_sql_field('src', $string);
-}
-
-=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ]
-
-Returns a text string representing this forward.
-
-END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
-history records.
-
-=cut
-
-sub label {
- my $self = shift;
- my $tag = '';
-
- if ( $self->srcsvc ) {
- my $svc_acct = $self->srcsvc_acct(@_);
- $tag = $svc_acct->email(@_);
- } else {
- $tag = $self->src;
- }
-
- $tag .= ' -> ';
-
- if ( $self->dstsvc ) {
- my $svc_acct = $self->dstsvc_acct(@_);
- $tag .= $svc_acct->email(@_);
- } else {
- $tag .= $self->dst;
- }
-
- $tag;
-}
-
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this mail forwarding alias to the database. If there is an error, returns
-the error, otherwise returns false.
-
-The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
-defined. An FS::cust_svc record will be created and inserted.
-
-Currently available options are: I<depend_jobnum>
-
-If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
-jobnums), all provisioning jobs will have a dependancy on the supplied
-jobnum(s) (they will not run until the specific job(s) complete(s)).
-
-=cut
-
-sub insert {
- my $self = shift;
- my $error;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- $error = $self->check;
- return $error if $error;
-
- $error = $self->SUPER::insert(@_);
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- ''; #no error
-
-}
-
-=item delete
-
-Deletes this mail forwarding alias from the database. If there is an error,
-returns the error, otherwise returns false.
-
-The corresponding FS::cust_svc record will be deleted as well.
-
-=cut
-
-sub delete {
- my $self = shift;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::Autocommit = 0;
- my $dbh = dbh;
-
- my $error = $self->SUPER::delete(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my ( $new, $old ) = ( shift, shift );
-
- if ( $new->srcsvc != $old->srcsvc
- && ( $new->dstsvc != $old->dstsvc
- || ! $new->dstsvc && $new->dst ne $old->dst
- )
- ) {
- return "Can't change both source and destination of a mail forward!"
- }
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my $error = $new->SUPER::replace($old, @_);
- if ($error) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-=item suspend
-
-Just returns false (no error) for now.
-
-Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item unsuspend
-
-Just returns false (no error) for now.
-
-Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item cancel
-
-Just returns false (no error) for now.
-
-Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item check
-
-Checks all fields to make sure this is a valid mail forwarding alias. If there
-is an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-Sets any fixed values; see L<FS::part_svc>.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $x = $self->setfixed;
- return $x unless ref($x);
- #my $part_svc = $x;
-
- my $error = $self->ut_numbern('svcnum')
- || $self->ut_numbern('srcsvc')
- || $self->ut_numbern('dstsvc')
- ;
- return $error if $error;
-
- return "Both srcsvc and src were defined; only one can be specified"
- if $self->srcsvc && $self->src;
-
- return "one of srcsvc or src is required"
- unless $self->srcsvc || $self->src;
-
- return "Unknown srcsvc: ". $self->srcsvc
- unless ! $self->srcsvc || $self->srcsvc_acct;
-
- return "Both dstsvc and dst were defined; only one can be specified"
- if $self->dstsvc && $self->dst;
-
- return "one of dstsvc or dst is required"
- unless $self->dstsvc || $self->dst;
-
- return "Unknown dstsvc: ". $self->dstsvc
- unless ! $self->dstsvc || $self->dstsvc_acct;
- #return "Unknown dstsvc"
- # unless qsearchs('svc_acct', { 'svcnum' => $self->dstsvc } )
- # || ! $self->dstsvc;
-
- if ( $self->src ) {
- $self->src =~ /^([\w\.\-\&]*)(\@([\w\-]+\.)+\w+)$/
- or return "Illegal src: ". $self->src;
- $self->src("$1$2");
- } else {
- $self->src('');
- }
-
- if ( $self->dst ) {
- my $conf = new FS::Conf;
- if ( $conf->exists('svc_forward-arbitrary_dst') ) {
- my $error = $self->ut_textn('dst');
- return $error if $error;
- } else {
- $self->dst =~ /^([\w\.\-\&]*)(\@([\w\-]+\.)+\w+)$/
- or return "Illegal dst: ". $self->dst;
- $self->dst("$1$2");
- }
- } else {
- $self->dst('');
- }
-
- $self->SUPER::check;
-}
-
-=item srcsvc_acct
-
-Returns the FS::svc_acct object referenced by the srcsvc column, or false for
-literally specified forwards.
-
-=cut
-
-sub srcsvc_acct {
- my $self = shift;
- qsearchs('svc_acct', { 'svcnum' => $self->srcsvc } );
-}
-
-=item dstsvc_acct
-
-Returns the FS::svc_acct object referenced by the srcsvc column, or false for
-literally specified forwards.
-
-=cut
-
-sub dstsvc_acct {
- my $self = shift;
- qsearchs('svc_acct', { 'svcnum' => $self->dstsvc } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::Conf>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>,
-L<FS::svc_acct>, L<FS::svc_domain>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm
deleted file mode 100644
index 00ccc19..0000000
--- a/FS/FS/svc_phone.pm
+++ /dev/null
@@ -1,190 +0,0 @@
-package FS::svc_phone;
-
-use strict;
-use vars qw( @ISA );
-#use FS::Record qw( qsearch qsearchs );
-use FS::svc_Common;
-
-@ISA = qw( FS::svc_Common );
-
-=head1 NAME
-
-FS::svc_phone - Object methods for svc_phone records
-
-=head1 SYNOPSIS
-
- use FS::svc_phone;
-
- $record = new FS::svc_phone \%hash;
- $record = new FS::svc_phone { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $error = $record->cancel;
-
-=head1 DESCRIPTION
-
-An FS::svc_phone object represents a phone number. FS::svc_phone inherits
-from FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item svcnum - primary key
-
-=item countrycode -
-
-=item phonenum -
-
-=item pin -
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new phone number. To add the number to the database, see L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-#
-sub table_info {
- {
- 'name' => 'Phone number',
- 'sorts' => 'phonenum',
- 'display_weight' => 60,
- 'cancel_weight' => 80,
- 'fields' => {
- 'countrycode' => { label => 'Country code',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- },
- 'phonenum' => 'Phone number',
- 'pin' => { label => 'Personal Identification Number',
- type => 'text',
- disable_inventory => 1,
- disable_select => 1,
- },
- },
- };
-}
-
-sub table { 'svc_phone'; }
-
-=item search_sql STRING
-
-Class method which returns an SQL fragment to search for the given string.
-
-=cut
-
-sub search_sql {
- my( $class, $string ) = @_;
- $class->search_sql_field('phonenum', $string );
-}
-
-=item label
-
-Returns the phone number.
-
-=cut
-
-sub label {
- my $self = shift;
- $self->phonenum; #XXX format it better
-}
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=cut
-
-# the insert method can be inherited from FS::Record
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-# the replace method can be inherited from FS::Record
-
-=item suspend
-
-Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item unsuspend
-
-Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item cancel
-
-Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item check
-
-Checks all fields to make sure this is a valid phone number. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('svcnum')
- || $self->ut_numbern('countrycode')
- || $self->ut_number('phonenum')
- || $self->ut_numbern('pin')
- ;
- return $error if $error;
-
- $self->countrycode(1) unless $self->countrycode;
-
- $self->SUPER::check;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
-L<FS::cust_pkg>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/svc_www.pm b/FS/FS/svc_www.pm
deleted file mode 100644
index 53225bb..0000000
--- a/FS/FS/svc_www.pm
+++ /dev/null
@@ -1,312 +0,0 @@
-package FS::svc_www;
-
-use strict;
-use vars qw(@ISA $conf $apacheip);
-#use FS::Record qw( qsearch qsearchs );
-use FS::Record qw( qsearchs dbh );
-use FS::svc_Common;
-use FS::cust_svc;
-use FS::domain_record;
-use FS::svc_acct;
-use FS::svc_domain;
-
-@ISA = qw( FS::svc_Common );
-
-#ask FS::UID to run this stuff for us later
-$FS::UID::callback{'FS::svc_www'} = sub {
- $conf = new FS::Conf;
- $apacheip = $conf->config('apacheip');
-};
-
-=head1 NAME
-
-FS::svc_www - Object methods for svc_www records
-
-=head1 SYNOPSIS
-
- use FS::svc_www;
-
- $record = new FS::svc_www \%hash;
- $record = new FS::svc_www { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
- $error = $record->suspend;
-
- $error = $record->unsuspend;
-
- $error = $record->cancel;
-
-=head1 DESCRIPTION
-
-An FS::svc_www object represents an web virtual host. FS::svc_www inherits
-from FS::svc_Common. The following fields are currently supported:
-
-=over 4
-
-=item svcnum - primary key
-
-=item recnum - DNS `A' record corresponding to this web virtual host. (see L<FS::domain_record>)
-
-=item usersvc - account (see L<FS::svc_acct>) corresponding to this web virtual host.
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new web virtual host. To add the record to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to. You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-sub table_info {
- {
- 'name' => 'Hosting',
- 'name_plural' => 'Virtual hosting services',
- 'display_weight' => 40,
- 'cancel_weight' => 20,
- 'fields' => {
- },
- };
-};
-
-sub table { 'svc_www'; }
-
-=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ]
-
-Returns the zone name for this virtual host.
-
-END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
-history records.
-
-=cut
-
-sub label {
- my $self = shift;
- $self->domain_record(@_)->zone;
-}
-
-=item insert [ , OPTION => VALUE ... ]
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
-defined. An FS::cust_svc record will be created and inserted.
-
-Currently available options are: I<depend_jobnum>
-
-If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
-jobnums), all provisioning jobs will have a dependancy on the supplied
-jobnum(s) (they will not run until the specific job(s) complete(s)).
-
-
-=cut
-
-sub insert {
- my $self = shift;
-
- my $error = $self->check;
- return $error if $error;
-
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- #if ( $self->recnum =~ /^([\w\-]+|\@)\.(([\w\.\-]+\.)+\w+)$/ ) {
- if ( $self->recnum =~ /^([\w\-]+|\@)\.(\d+)$/ ) {
- my( $reczone, $domain_svcnum ) = ( $1, $2 );
- unless ( $apacheip ) {
- $dbh->rollback if $oldAutoCommit;
- return "Configuration option apacheip not set; can't autocreate A record";
- #"for $reczone". $svc_domain->domain;
- }
- my $domain_record = new FS::domain_record {
- 'svcnum' => $domain_svcnum,
- 'reczone' => $reczone,
- 'recaf' => 'IN',
- 'rectype' => 'A',
- 'recdata' => $apacheip,
- };
- $error = $domain_record->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- $self->recnum($domain_record->recnum);
- }
-
- $error = $self->SUPER::insert(@_);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
-}
-
-=item delete
-
-Delete this record from the database.
-
-=cut
-
-sub delete {
- my $self = shift;
- my $error;
-
- $error = $self->SUPER::delete(@_);
- return $error if $error;
-
- '';
-}
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=cut
-
-sub replace {
- my ( $new, $old ) = ( shift, shift );
- my $error;
-
- $error = $new->SUPER::replace($old, @_);
- return $error if $error;
-
- '';
-}
-
-=item suspend
-
-Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item unsuspend
-
-Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item cancel
-
-Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
-
-=item check
-
-Checks all fields to make sure this is a valid web virtual host. If there is
-an error, returns the error, otherwise returns false. Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $x = $self->setfixed;
- return $x unless ref($x);
- #my $part_svc = $x;
-
- my $error =
- $self->ut_numbern('svcnum')
-# || $self->ut_number('recnum')
- || $self->ut_numbern('usersvc')
- || $self->ut_anything('config')
- ;
- return $error if $error;
-
- if ( $self->recnum =~ /^(\d+)$/ ) {
-
- $self->recnum($1);
- return "Unknown recnum: ". $self->recnum
- unless qsearchs('domain_record', { 'recnum' => $self->recnum } );
-
- } elsif ( $self->recnum =~ /^([\w\-]+|\@)\.(([\w\.\-]+\.)+\w+)$/ ) {
-
- my( $reczone, $domain ) = ( $1, $2 );
-
- my $svc_domain = qsearchs( 'svc_domain', { 'domain' => $domain } )
- or return "unknown domain $domain (recnum $1.$2)";
-
- my $domain_record = qsearchs( 'domain_record', {
- 'reczone' => $reczone,
- 'svcnum' => $svc_domain->svcnum,
- });
-
- if ( $domain_record ) {
- $self->recnum($domain_record->recnum);
- } else {
- #insert will create it
- #$self->recnum("$reczone.$domain");
- $self->recnum("$reczone.". $svc_domain->svcnum);
- }
-
- } else {
- return "Illegal recnum: ". $self->recnum;
- }
-
- if ( $self->usersvc ) {
- return "Unknown usersvc0 (svc_acct.svcnum): ". $self->usersvc
- unless qsearchs('svc_acct', { 'svcnum' => $self->usersvc } );
- }
-
- $self->SUPER::check;
-
-}
-
-=item domain_record
-
-Returns the FS::domain_record record for this web virtual host's zone (see
-L<FS::domain_record>).
-
-=cut
-
-sub domain_record {
- my $self = shift;
- qsearchs('domain_record', { 'recnum' => $self->recnum } );
-}
-
-=item svc_acct
-
-Returns the FS::svc_acct record for this web virtual host's owner (see
-L<FS::svc_acct>).
-
-=cut
-
-sub svc_acct {
- my $self = shift;
- qsearchs('svc_acct', { 'svcnum' => $self->usersvc } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::svc_Common>, L<FS::Record>, L<FS::domain_record>, L<FS::cust_svc>,
-L<FS::part_svc>, L<FS::cust_pkg>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/type_pkgs.pm b/FS/FS/type_pkgs.pm
deleted file mode 100644
index bf34e7c..0000000
--- a/FS/FS/type_pkgs.pm
+++ /dev/null
@@ -1,125 +0,0 @@
-package FS::type_pkgs;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearchs );
-use FS::agent_type;
-use FS::part_pkg;
-
-@ISA = qw( FS::Record );
-
-=head1 NAME
-
-FS::type_pkgs - Object methods for type_pkgs records
-
-=head1 SYNOPSIS
-
- use FS::type_pkgs;
-
- $record = new FS::type_pkgs \%hash;
- $record = new FS::type_pkgs { 'column' => 'value' };
-
- $error = $record->insert;
-
- $error = $new_record->replace($old_record);
-
- $error = $record->delete;
-
- $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::type_pkgs record links an agent type (see L<FS::agent_type>) to a
-billing item definition (see L<FS::part_pkg>). FS::type_pkgs inherits from
-FS::Record. The following fields are currently supported:
-
-=over 4
-
-=item typepkgnum - primary key
-
-=item typenum - Agent type, see L<FS::agent_type>
-
-=item pkgpart - Billing item definition, see L<FS::part_pkg>
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Create a new record. To add the record to the database, see L<"insert">.
-
-=cut
-
-sub table { 'type_pkgs'; }
-
-=item insert
-
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Deletes this record from the database. If there is an error, returns the
-error, otherwise returns false.
-
-=item replace OLD_RECORD
-
-Replaces OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid record. If there is an error,
-returns the error, otherwise returns false. Called by the insert and replace
-methods.
-
-=cut
-
-sub check {
- my $self = shift;
-
- my $error =
- $self->ut_numbern('typepkgnum')
- || $self->ut_number('typenum')
- || $self->ut_number('pkgpart')
- ;
- return $error if $error;
-
- return "Unknown typenum"
- unless qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
-
- return "Unknown pkgpart"
- unless qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
-
- $self->SUPER::check;
-}
-
-=item part_pkg
-
-Returns the FS::part_pkg object associated with this record.
-
-=cut
-
-sub part_pkg {
- my $self = shift;
- qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
-}
-
-=cut
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, L<FS::agent_type>, L<FS::part_pkgs>, schema.html from the base
-documentation.
-
-=cut
-
-1;
-