From c82d349f864e6bd9f96fd1156903bc1f7193a203 Mon Sep 17 00:00:00 2001 From: cvs2git Date: Mon, 27 Dec 2010 00:04:45 +0000 Subject: This commit was manufactured by cvs2svn to create tag 'TORRUS_1_0_9'. --- FS/Changes | 5 - FS/FS.pm | 524 --- FS/FS/AccessRight.pm | 401 -- FS/FS/CGI.pm | 333 -- FS/FS/ClientAPI.pm | 42 - FS/FS/ClientAPI/Agent.pm | 214 - FS/FS/ClientAPI/Bulk.pm | 384 -- FS/FS/ClientAPI/MasonComponent.pm | 131 - FS/FS/ClientAPI/MyAccount.pm | 2039 --------- FS/FS/ClientAPI/PrepaidPhone.pm | 253 - FS/FS/ClientAPI/SGNG.pm | 277 -- FS/FS/ClientAPI/Signup.pm | 888 ---- FS/FS/ClientAPI/passwd.pm | 46 - FS/FS/ClientAPI_SessionCache.pm | 79 - FS/FS/ClientAPI_XMLRPC.pm | 148 - FS/FS/Conf.pm | 4235 ----------------- FS/FS/ConfDefaults.pm | 86 - FS/FS/ConfItem.pm | 63 - FS/FS/Conf_compat17.pm | 2520 ---------- FS/FS/Cron/alert_expiration.pm | 189 - FS/FS/Cron/backup.pm | 45 - FS/FS/Cron/bill.pm | 245 - FS/FS/Cron/breakage.pm | 84 - FS/FS/Cron/check.pm | 200 - FS/FS/Cron/expire_user_pref.pm | 20 - FS/FS/Cron/notify.pm | 159 - FS/FS/Cron/upload.pm | 176 - FS/FS/Cron/vacuum.pm | 23 - FS/FS/CurrentUser.pm | 67 - FS/FS/Daemon.pm | 120 - FS/FS/InitHandler.pm | 91 - FS/FS/Maestro.pm | 248 - FS/FS/Mason.pm | 555 --- FS/FS/Mason/Request.pm | 87 - FS/FS/Misc.pm | 904 ---- FS/FS/Misc/DateTime.pm | 64 - FS/FS/Misc/eps2png.pm | 278 -- FS/FS/Misc/prune.pm | 131 - FS/FS/Msgcat.pm | 100 - FS/FS/Pony.pm | 23 - FS/FS/Record.pm | 3157 ------------- FS/FS/Report.pm | 46 - FS/FS/Report/FCC_477.pm | 90 - FS/FS/Report/Table.pm | 27 - FS/FS/Report/Table/Monthly.pm | 590 --- FS/FS/Schema.pm | 3171 ------------- FS/FS/SearchCache.pm | 96 - FS/FS/Setup.pm | 552 --- FS/FS/TicketSystem.pm | 52 - FS/FS/TicketSystem/RT_External.pm | 401 -- FS/FS/TicketSystem/RT_Internal.pm | 402 -- FS/FS/TicketSystem/RT_Libs.pm | 10 - FS/FS/Tron.pm | 123 - FS/FS/UI/Web.pm | 644 --- FS/FS/UI/Web/small_custview.pm | 149 - FS/FS/UI/bytecount.pm | 101 - FS/FS/UID.pm | 405 -- FS/FS/Upgrade.pm | 378 -- FS/FS/XMLRPC.pm | 166 - FS/FS/Yori.pm | 94 - FS/FS/access_group.pm | 162 - FS/FS/access_groupagent.pm | 146 - FS/FS/access_right.pm | 198 - FS/FS/access_user.pm | 544 --- FS/FS/access_user_pref.pm | 129 - FS/FS/access_usergroup.pm | 143 - FS/FS/acct_rt_transaction.pm | 316 -- FS/FS/acct_snarf.pm | 215 - FS/FS/addr_block.pm | 385 -- FS/FS/agent.pm | 592 --- FS/FS/agent_payment_gateway.pm | 139 - FS/FS/agent_type.pm | 195 - FS/FS/banned_pay.pm | 141 - FS/FS/bill_batch.pm | 151 - FS/FS/category_Common.pm | 87 - FS/FS/cdr.pm | 950 ---- FS/FS/cdr/asterisk.pm | 45 - FS/FS/cdr/bell_west.pm | 122 - FS/FS/cdr/broadsoft.pm | 108 - FS/FS/cdr/cia.pm | 39 - FS/FS/cdr/genband.pm | 120 - FS/FS/cdr/genband_meetme.pm | 17 - FS/FS/cdr/indosoft.pm | 71 - FS/FS/cdr/infinite.pm | 41 - FS/FS/cdr/netcentrex.pm | 783 ---- FS/FS/cdr/nextone.pm | 26 - FS/FS/cdr/openser.pm | 24 - FS/FS/cdr/sansay.pm | 408 -- FS/FS/cdr/simple.pm | 52 - FS/FS/cdr/simple2.pm | 51 - FS/FS/cdr/taqua.pm | 190 - FS/FS/cdr/taqua_om.pm | 19 - FS/FS/cdr/telos_csv.pm | 60 - FS/FS/cdr/telos_xml.pm | 36 - FS/FS/cdr/transnexus.pm | 66 - FS/FS/cdr/troop.pm | 128 - FS/FS/cdr/unitel.pm | 39 - FS/FS/cdr/vitelity.pm | 25 - FS/FS/cdr/wip.pm | 48 - FS/FS/cdr_batch.pm | 128 - FS/FS/cdr_calltype.pm | 115 - FS/FS/cdr_carrier.pm | 116 - FS/FS/cdr_termination.pm | 155 - FS/FS/cdr_type.pm | 119 - FS/FS/cgp_rule.pm | 363 -- FS/FS/cgp_rule_action.pm | 141 - FS/FS/cgp_rule_condition.pm | 148 - FS/FS/class_Common.pm | 143 - FS/FS/clientapi_session.pm | 121 - FS/FS/clientapi_session_field.pm | 124 - FS/FS/conf.pm | 114 - FS/FS/contact.pm | 300 -- FS/FS/contact_email.pm | 128 - FS/FS/contact_phone.pm | 143 - FS/FS/cust_attachment.pm | 199 - FS/FS/cust_bill.pm | 4710 ------------------- FS/FS/cust_bill_ApplicationCommon.pm | 518 --- FS/FS/cust_bill_batch.pm | 70 - FS/FS/cust_bill_batch_option.pm | 126 - FS/FS/cust_bill_event.pm | 380 -- FS/FS/cust_bill_pay.pm | 186 - FS/FS/cust_bill_pay_batch.pm | 120 - FS/FS/cust_bill_pay_pkg.pm | 224 - FS/FS/cust_bill_pkg.pm | 902 ---- FS/FS/cust_bill_pkg_detail.pm | 376 -- FS/FS/cust_bill_pkg_discount.pm | 158 - FS/FS/cust_bill_pkg_display.pm | 166 - FS/FS/cust_bill_pkg_tax_location.pm | 225 - FS/FS/cust_bill_pkg_tax_rate_location.pm | 221 - FS/FS/cust_category.pm | 97 - FS/FS/cust_class.pm | 120 - FS/FS/cust_credit.pm | 639 --- FS/FS/cust_credit_bill.pm | 170 - FS/FS/cust_credit_bill_pkg.pm | 355 -- FS/FS/cust_credit_refund.pm | 186 - FS/FS/cust_event.pm | 508 -- FS/FS/cust_location.pm | 278 -- FS/FS/cust_main.pm | 4830 -------------------- FS/FS/cust_main/Billing.pm | 2111 --------- FS/FS/cust_main/Billing_Discount.pm | 207 - FS/FS/cust_main/Billing_Realtime.pm | 1494 ------ FS/FS/cust_main/Import.pm | 472 -- FS/FS/cust_main/Packages.pm | 452 -- FS/FS/cust_main/Search.pm | 881 ---- FS/FS/cust_main/_Marketgear.pm | 146 - FS/FS/cust_main_Mixin.pm | 554 --- FS/FS/cust_main_county.pm | 506 -- FS/FS/cust_main_exemption.pm | 128 - FS/FS/cust_main_invoice.pm | 188 - FS/FS/cust_main_note.pm | 193 - FS/FS/cust_note_class.pm | 105 - FS/FS/cust_pay.pm | 1061 ----- FS/FS/cust_pay_batch.pm | 353 -- FS/FS/cust_pay_pending.pm | 341 -- FS/FS/cust_pay_refund.pm | 188 - FS/FS/cust_pay_void.pm | 291 -- FS/FS/cust_pkg.pm | 3435 -------------- FS/FS/cust_pkg/Import.pm | 373 -- FS/FS/cust_pkg_detail.pm | 140 - FS/FS/cust_pkg_discount.pm | 246 - FS/FS/cust_pkg_option.pm | 115 - FS/FS/cust_pkg_reason.pm | 331 -- FS/FS/cust_recon.pm | 193 - FS/FS/cust_refund.pm | 394 -- FS/FS/cust_statement.pm | 272 -- FS/FS/cust_svc.pm | 775 ---- FS/FS/cust_svc_option.pm | 134 - FS/FS/cust_tag.pm | 147 - FS/FS/cust_tax_adjustment.pm | 149 - FS/FS/cust_tax_exempt.pm | 152 - FS/FS/cust_tax_exempt_pkg.pm | 152 - FS/FS/cust_tax_location.pm | 344 -- FS/FS/discount.pm | 193 - FS/FS/domain_record.pm | 465 -- FS/FS/dsl_note.pm | 127 - FS/FS/export_device.pm | 136 - FS/FS/export_svc.pm | 322 -- FS/FS/geocode_Mixin.pm | 164 - FS/FS/h_Common.pm | 124 - FS/FS/h_cust_bill.pm | 33 - FS/FS/h_cust_credit.pm | 33 - FS/FS/h_cust_pay.pm | 33 - FS/FS/h_cust_pkg.pm | 34 - FS/FS/h_cust_pkg_reason.pm | 34 - FS/FS/h_cust_svc.pm | 165 - FS/FS/h_cust_tax_exempt.pm | 40 - FS/FS/h_domain_record.pm | 33 - FS/FS/h_inventory_item.pm | 33 - FS/FS/h_svc_acct.pm | 78 - FS/FS/h_svc_broadband.pm | 33 - FS/FS/h_svc_domain.pm | 33 - FS/FS/h_svc_dsl.pm | 33 - FS/FS/h_svc_external.pm | 33 - FS/FS/h_svc_forward.pm | 85 - FS/FS/h_svc_mailinglist.pm | 33 - FS/FS/h_svc_pbx.pm | 33 - FS/FS/h_svc_phone.pm | 33 - FS/FS/h_svc_www.pm | 67 - FS/FS/inventory_class.pm | 264 -- FS/FS/inventory_item.pm | 182 - FS/FS/location_Mixin.pm | 57 - FS/FS/m2m_Common.pm | 170 - FS/FS/m2name_Common.pm | 177 - FS/FS/mailinglist.pm | 173 - FS/FS/mailinglistmember.pm | 245 - FS/FS/msg_template.pm | 572 --- FS/FS/msgcat.pm | 166 - FS/FS/nas.pm | 150 - FS/FS/o2m_Common.pm | 152 - FS/FS/option_Common.pm | 352 -- FS/FS/otaker_Mixin.pm | 84 - FS/FS/part_bill_event.pm | 368 -- FS/FS/part_device.pm | 148 - FS/FS/part_event.pm | 444 -- FS/FS/part_event/Action.pm | 240 - FS/FS/part_event/Action/Mixin/credit_pkg.pm | 63 - FS/FS/part_event/Action/addpost.pm | 20 - FS/FS/part_event/Action/apply.pm | 24 - FS/FS/part_event/Action/bill.pm | 26 - FS/FS/part_event/Action/cancel.pm | 30 - FS/FS/part_event/Action/collect.pm | 26 - FS/FS/part_event/Action/cust_bill_batch.pm | 25 - FS/FS/part_event/Action/cust_bill_comp.pm | 28 - FS/FS/part_event/Action/cust_bill_email.pm | 23 - FS/FS/part_event/Action/cust_bill_fee_percent.pm | 28 - FS/FS/part_event/Action/cust_bill_realtime_card.pm | 28 - .../part_event/Action/cust_bill_realtime_check.pm | 28 - FS/FS/part_event/Action/cust_bill_realtime_lec.pm | 28 - FS/FS/part_event/Action/cust_bill_send.pm | 20 - FS/FS/part_event/Action/cust_bill_send_agent.pm | 42 - .../part_event/Action/cust_bill_send_alternate.pm | 31 - FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm | 50 - .../part_event/Action/cust_bill_send_if_newest.pm | 38 - FS/FS/part_event/Action/cust_bill_send_reminder.pm | 31 - FS/FS/part_event/Action/cust_bill_spool_csv.pm | 59 - .../Action/cust_bill_suspend_if_balance.pm | 42 - FS/FS/part_event/Action/cust_statement.pm | 39 - FS/FS/part_event/Action/cust_statement_send.pm | 26 - FS/FS/part_event/Action/fee.pm | 58 - FS/FS/part_event/Action/notice.pm | 47 - FS/FS/part_event/Action/notice_to.pm | 55 - FS/FS/part_event/Action/pkg_agent_credit.pm | 39 - FS/FS/part_event/Action/pkg_agent_credit_pkg.pm | 9 - FS/FS/part_event/Action/pkg_cancel.pm | 32 - FS/FS/part_event/Action/pkg_employee_credit.pm | 39 - FS/FS/part_event/Action/pkg_employee_credit_pkg.pm | 9 - FS/FS/part_event/Action/pkg_referral_credit.pm | 62 - FS/FS/part_event/Action/pkg_referral_credit_pkg.pm | 9 - FS/FS/part_event/Action/suspend.pm | 32 - FS/FS/part_event/Action/suspend_if_pkgpart.pm | 40 - FS/FS/part_event/Action/suspend_unless_pkgpart.pm | 40 - FS/FS/part_event/Action/writeoff.pm | 33 - FS/FS/part_event/Condition.pm | 470 -- FS/FS/part_event/Condition/agent.pm | 37 - FS/FS/part_event/Condition/agent_type.pm | 40 - FS/FS/part_event/Condition/balance.pm | 48 - FS/FS/part_event/Condition/balance_age.pm | 52 - FS/FS/part_event/Condition/balance_credit_limit.pm | 32 - FS/FS/part_event/Condition/balance_under.pm | 42 - FS/FS/part_event/Condition/cust_bill_age.pm | 46 - FS/FS/part_event/Condition/cust_bill_has_noauto.pm | 33 - .../part_event/Condition/cust_bill_has_service.pm | 56 - .../part_event/Condition/cust_bill_hasnt_noauto.pm | 33 - FS/FS/part_event/Condition/cust_bill_owed.pm | 54 - FS/FS/part_event/Condition/cust_bill_owed_under.pm | 49 - FS/FS/part_event/Condition/cust_bill_past_due.pm | 41 - .../Condition/cust_pay_batch_declined.pm | 51 - FS/FS/part_event/Condition/cust_payments.pm | 43 - FS/FS/part_event/Condition/cust_payments_pkg.pm | 68 - FS/FS/part_event/Condition/cust_status.pm | 40 - FS/FS/part_event/Condition/dundate.pm | 26 - FS/FS/part_event/Condition/every.pm | 67 - FS/FS/part_event/Condition/has_pkg_class.pm | 40 - FS/FS/part_event/Condition/has_pkgpart.pm | 41 - FS/FS/part_event/Condition/has_referral_custnum.pm | 50 - FS/FS/part_event/Condition/hasnt_pkgpart.pm | 40 - FS/FS/part_event/Condition/once.pm | 55 - FS/FS/part_event/Condition/once_every.pm | 46 - FS/FS/part_event/Condition/once_percust.pm | 67 - FS/FS/part_event/Condition/once_perinv.pm | 57 - FS/FS/part_event/Condition/payby.pm | 44 - FS/FS/part_event/Condition/pkg_age.pm | 66 - FS/FS/part_event/Condition/pkg_class.pm | 38 - FS/FS/part_event/Condition/pkg_freq.pm | 36 - FS/FS/part_event/Condition/pkg_next_bill_within.pm | 51 - FS/FS/part_event/Condition/pkg_notchange.pm | 31 - FS/FS/part_event/Condition/pkg_pkgpart.pm | 39 - FS/FS/part_event/Condition/pkg_recurring.pm | 28 - FS/FS/part_event/Condition/pkg_status.pm | 44 - FS/FS/part_event/Condition/pkg_unless_pkgpart.pm | 39 - FS/FS/part_event_condition.pm | 354 -- FS/FS/part_event_condition_option.pm | 151 - FS/FS/part_event_condition_option_option.pm | 129 - FS/FS/part_event_option.pm | 214 - FS/FS/part_export.pm | 493 -- FS/FS/part_export/acct_freeside.pm | 139 - FS/FS/part_export/acct_http.pm | 63 - FS/FS/part_export/acct_plesk.pm | 121 - FS/FS/part_export/acct_sql.pm | 310 -- FS/FS/part_export/amazon_ec2.pm | 169 - FS/FS/part_export/apache.pm | 47 - FS/FS/part_export/artera_turbo.pm | 181 - FS/FS/part_export/bind.pm | 35 - FS/FS/part_export/bind_slave.pm | 28 - FS/FS/part_export/bsdshell.pm | 25 - FS/FS/part_export/cardfortress.pm | 64 - FS/FS/part_export/communigate_pro.pm | 1070 ----- FS/FS/part_export/communigate_pro_singledomain.pm | 37 - FS/FS/part_export/cp.pm | 161 - FS/FS/part_export/cpanel.pm | 192 - FS/FS/part_export/cust_http.pm | 67 - FS/FS/part_export/cyrus.pm | 120 - FS/FS/part_export/dashcs_e911.pm | 153 - FS/FS/part_export/domain_shellcommands.pm | 165 - FS/FS/part_export/domain_sql.pm | 241 - FS/FS/part_export/domreg_net_dri.pm | 614 --- FS/FS/part_export/domreg_opensrs.pm | 616 --- FS/FS/part_export/everyone_net.pm | 132 - FS/FS/part_export/forward_shellcommands.pm | 182 - FS/FS/part_export/globalpops_voip.pm | 370 -- FS/FS/part_export/grandstream.pm | 257 -- FS/FS/part_export/http.pm | 151 - FS/FS/part_export/ikano.pm | 697 --- FS/FS/part_export/indosoft.pm | 219 - FS/FS/part_export/infostreet.pm | 277 -- FS/FS/part_export/internal_diddb.pm | 134 - FS/FS/part_export/ldap.pm | 264 -- FS/FS/part_export/nas_wrapper.pm | 311 -- FS/FS/part_export/netsapiens.pm | 312 -- FS/FS/part_export/null.pm | 13 - FS/FS/part_export/passwdfile.pm | 18 - FS/FS/part_export/phone_shellcommands.pm | 140 - FS/FS/part_export/phone_sqlradius.pm | 158 - FS/FS/part_export/postfix.pm | 32 - FS/FS/part_export/prizm.pm | 591 --- FS/FS/part_export/radiator.pm | 167 - FS/FS/part_export/router.pm | 375 -- FS/FS/part_export/rt_ticket.pm | 219 - FS/FS/part_export/shellcommands.pm | 480 -- FS/FS/part_export/shellcommands_withdomain.pm | 138 - FS/FS/part_export/snmp.pm | 256 -- FS/FS/part_export/soma.pm | 412 -- FS/FS/part_export/sqlmail.pm | 220 - FS/FS/part_export/sqlradius.pm | 861 ---- FS/FS/part_export/sqlradius_withdomain.pm | 28 - FS/FS/part_export/sysvshell.pm | 25 - FS/FS/part_export/textradius.pm | 191 - FS/FS/part_export/thirdlane.pm | 348 -- FS/FS/part_export/trango.pm | 434 -- FS/FS/part_export/vitelity.pm | 250 - FS/FS/part_export/vpopmail.pm | 254 - FS/FS/part_export/www_plesk.pm | 138 - FS/FS/part_export/www_shellcommands.pm | 190 - FS/FS/part_export_option.pm | 134 - FS/FS/part_pkg.pm | 1643 ------- FS/FS/part_pkg/agent.pm | 172 - FS/FS/part_pkg/base_delayed.pm | 42 - FS/FS/part_pkg/base_rate.pm | 83 - FS/FS/part_pkg/bulk.pm | 130 - FS/FS/part_pkg/cdr_termination.pm | 204 - FS/FS/part_pkg/discount_Mixin.pm | 128 - FS/FS/part_pkg/flat.pm | 204 - FS/FS/part_pkg/flat_comission.pm | 60 - FS/FS/part_pkg/flat_comission_cust.pm | 44 - FS/FS/part_pkg/flat_comission_pkg.pm | 38 - FS/FS/part_pkg/flat_delayed.pm | 54 - FS/FS/part_pkg/flat_introrate.pm | 60 - FS/FS/part_pkg/global_Mixin.pm | 38 - FS/FS/part_pkg/incomplete/billoneday.pm | 48 - FS/FS/part_pkg/prepaid.pm | 51 - FS/FS/part_pkg/prorate.pm | 43 - FS/FS/part_pkg/prorate_Mixin.pm | 105 - FS/FS/part_pkg/prorate_delayed.pm | 53 - FS/FS/part_pkg/recur_Common.pm | 70 - FS/FS/part_pkg/rt_time.pm | 73 - FS/FS/part_pkg/sesmon_hour.pm | 50 - FS/FS/part_pkg/sesmon_minute.pm | 49 - FS/FS/part_pkg/sql_external.pm | 77 - FS/FS/part_pkg/sql_generic.pm | 81 - FS/FS/part_pkg/sqlradacct_hour.pm | 163 - FS/FS/part_pkg/subscription.pm | 108 - FS/FS/part_pkg/usage_Mixin.pm | 77 - FS/FS/part_pkg/voip_cdr.pm | 925 ---- FS/FS/part_pkg/voip_inbound.pm | 366 -- FS/FS/part_pkg/voip_sqlradacct.pm | 185 - FS/FS/part_pkg_discount.pm | 129 - FS/FS/part_pkg_link.pm | 163 - FS/FS/part_pkg_option.pm | 159 - FS/FS/part_pkg_report_option.pm | 125 - FS/FS/part_pkg_taxclass.pm | 226 - FS/FS/part_pkg_taxoverride.pm | 119 - FS/FS/part_pkg_taxproduct.pm | 139 - FS/FS/part_pkg_taxrate.pm | 420 -- FS/FS/part_pkg_vendor.pm | 140 - FS/FS/part_pop_local.pm | 113 - FS/FS/part_referral.pm | 208 - FS/FS/part_svc.pm | 881 ---- FS/FS/part_svc_column.pm | 123 - FS/FS/part_svc_router.pm | 33 - FS/FS/part_tag.pm | 132 - FS/FS/part_virtual_field.pm | 301 -- FS/FS/pay_batch.pm | 589 --- FS/FS/pay_batch/BoM.pm | 73 - FS/FS/pay_batch/PAP.pm | 103 - FS/FS/pay_batch/RBC.pm | 143 - FS/FS/pay_batch/ach_spiritone.pm | 65 - FS/FS/pay_batch/chase_canada.pm | 89 - FS/FS/pay_batch/paymentech.pm | 144 - FS/FS/pay_batch/td_canada_trust.pm | 90 - FS/FS/pay_batch/td_eft1464.pm | 156 - FS/FS/pay_batch/td_eftack264.pm | 59 - FS/FS/pay_batch/td_eftret80.pm | 46 - FS/FS/payby.pm | 209 - FS/FS/payinfo_Mixin.pm | 268 -- FS/FS/payinfo_transaction_Mixin.pm | 123 - FS/FS/payment_gateway.pm | 247 - FS/FS/payment_gateway_option.pm | 126 - FS/FS/phone_avail.pm | 217 - FS/FS/phone_device.pm | 299 -- FS/FS/phone_type.pm | 137 - FS/FS/pkg_category.pm | 132 - FS/FS/pkg_class.pm | 119 - FS/FS/pkg_referral.pm | 126 - FS/FS/pkg_svc.pm | 163 - FS/FS/port.pm | 154 - FS/FS/prepay_credit.pm | 203 - FS/FS/prospect_main.pm | 292 -- FS/FS/qual.pm | 191 - FS/FS/qual_option.pm | 128 - FS/FS/queue.pm | 526 --- FS/FS/queue_arg.pm | 120 - FS/FS/queue_depend.pm | 121 - FS/FS/raddb.pm | 1912 -------- FS/FS/radius_usergroup.pm | 131 - FS/FS/rate.pm | 450 -- FS/FS/rate_detail.pm | 640 --- FS/FS/rate_prefix.pm | 160 - FS/FS/rate_region.pm | 315 -- FS/FS/rate_time.pm | 168 - FS/FS/rate_time_interval.pm | 178 - FS/FS/reason.pm | 130 - FS/FS/reason_type.pm | 209 - FS/FS/reg_code.pm | 223 - FS/FS/reg_code_pkg.pm | 139 - FS/FS/registrar.pm | 119 - FS/FS/router.pm | 152 - FS/FS/session.pm | 265 -- FS/FS/svc_CGPRule_Mixin.pm | 61 - FS/FS/svc_CGP_Mixin.pm | 160 - FS/FS/svc_Common.pm | 1106 ----- FS/FS/svc_Domain_Mixin.pm | 134 - FS/FS/svc_External_Common.pm | 199 - FS/FS/svc_Parent_Mixin.pm | 103 - FS/FS/svc_acct.pm | 3249 ------------- FS/FS/svc_acct_pop.pm | 206 - FS/FS/svc_broadband.pm | 490 -- FS/FS/svc_cert.pm | 408 -- FS/FS/svc_domain.pm | 701 --- FS/FS/svc_dsl.pm | 299 -- FS/FS/svc_external.pm | 205 - FS/FS/svc_forward.pm | 368 -- FS/FS/svc_mailinglist.pm | 330 -- FS/FS/svc_pbx.pm | 370 -- FS/FS/svc_phone.pm | 675 --- FS/FS/svc_www.pm | 286 -- FS/FS/tax_class.pm | 392 -- FS/FS/tax_rate.pm | 2087 --------- FS/FS/tax_rate_location.pm | 317 -- FS/FS/type_pkgs.pm | 130 - FS/FS/usage_class.pm | 470 -- FS/MANIFEST | 554 --- FS/MANIFEST.SKIP | 1 - FS/Makefile.PL | 10 - FS/bin/freeside-addgroup | 50 - FS/bin/freeside-addoutsource | 32 - FS/bin/freeside-addoutsourceuser | 18 - FS/bin/freeside-adduser | 119 - FS/bin/freeside-apply-credits | 21 - FS/bin/freeside-apply_payments_and_credits | 79 - FS/bin/freeside-cdr-sftp_and_import | 204 - FS/bin/freeside-cdrd | 160 - FS/bin/freeside-cdrrewrited | 129 - FS/bin/freeside-check | 31 - FS/bin/freeside-count-active-customers | 17 - FS/bin/freeside-daily | 135 - FS/bin/freeside-dbdef-create | 47 - FS/bin/freeside-dedup-cust_bill_pkg_detail-header | 57 - FS/bin/freeside-delete-addr_blocks | 31 - FS/bin/freeside-deloutsource | 14 - FS/bin/freeside-deloutsourceuser | 6 - FS/bin/freeside-deluser | 64 - FS/bin/freeside-disable-reasons | 64 - FS/bin/freeside-email | 55 - FS/bin/freeside-fetch | 93 - FS/bin/freeside-history-requeue | 100 - FS/bin/freeside-init-config | 45 - FS/bin/freeside-monthly | 94 - FS/bin/freeside-paymentech-download | 137 - FS/bin/freeside-paymentech-upload | 133 - FS/bin/freeside-prepaidd | 115 - FS/bin/freeside-prune-applications | 63 - FS/bin/freeside-pull-dsl | 71 - FS/bin/freeside-queued | 298 -- FS/bin/freeside-radgroup | 76 - FS/bin/freeside-reexport | 71 - FS/bin/freeside-reset-fixed | 69 - FS/bin/freeside-selfservice-server | 272 -- FS/bin/freeside-selfservice-xmlrpcd | 351 -- FS/bin/freeside-setinvoice | 42 - FS/bin/freeside-setup | 167 - FS/bin/freeside-sqlradius-dedup-group | 82 - FS/bin/freeside-sqlradius-radacctd | 145 - FS/bin/freeside-sqlradius-reset | 118 - FS/bin/freeside-sqlradius-seconds | 58 - FS/bin/freeside-sqlradius-set-lastlog | 102 - FS/bin/freeside-upgrade | 296 -- FS/bin/freeside-void-payments | 239 - FS/bin/freeside-wipe-cvv | 87 - FS/bin/freeside-yori | 16 - FS/t/AccessRight.t | 5 - FS/t/CGI.t | 5 - FS/t/ClientAPI.t | 5 - FS/t/ClientAPI_SessionCache.t | 5 - FS/t/Conf.t | 5 - FS/t/ConfDefaults.t | 5 - FS/t/ConfItem.t | 5 - FS/t/Cron-backup.t | 5 - FS/t/Cron-bill.t | 5 - FS/t/Cron-vacuum.t | 5 - FS/t/Daemon.t | 5 - FS/t/InitHandler.t | 5 - FS/t/Misc.t | 5 - FS/t/Msgcat.t | 5 - FS/t/Record.t | 5 - FS/t/Report-FCC_477.t | 5 - FS/t/Report-Table-Monthly.t | 5 - FS/t/Report-Table.t | 5 - FS/t/Report.t | 5 - FS/t/SearchCache.t | 5 - FS/t/UID.t | 5 - FS/t/access_group.t | 5 - FS/t/access_groupagent.t | 5 - FS/t/access_right.t | 5 - FS/t/access_user.t | 5 - FS/t/access_user_pref.t | 5 - FS/t/access_usergroup.t | 5 - FS/t/acct_rt_transaction.t | 5 - FS/t/acct_snarf.t | 5 - FS/t/addr_block.t | 5 - FS/t/agent.t | 5 - FS/t/agent_payment_gateway.t | 5 - FS/t/agent_type.t | 5 - FS/t/banned_pay.t | 5 - FS/t/category_Common.t | 5 - FS/t/cdr.t | 5 - FS/t/cdr_batch.t | 5 - FS/t/cdr_calltype.t | 5 - FS/t/cdr_carrier.t | 5 - FS/t/cdr_termination.t | 5 - FS/t/cdr_type.t | 5 - FS/t/cgp_rule.t | 5 - FS/t/cgp_rule_action.t | 5 - FS/t/cgp_rule_condition.t | 5 - FS/t/class_Common.t | 5 - FS/t/clientapi_session.t | 5 - FS/t/clientapi_session_field.t | 5 - FS/t/conf.t | 5 - FS/t/contact.t | 5 - FS/t/contact_email.t | 5 - FS/t/contact_phone.t | 5 - FS/t/cust_attachment.t | 5 - FS/t/cust_bill.t | 5 - FS/t/cust_bill_ApplicationCommon.t | 5 - FS/t/cust_bill_event.t | 5 - FS/t/cust_bill_pay.t | 5 - FS/t/cust_bill_pay_batch.t | 5 - FS/t/cust_bill_pay_pkg.t | 5 - FS/t/cust_bill_pkg.t | 5 - FS/t/cust_bill_pkg_detail.t | 5 - FS/t/cust_bill_pkg_discount.t | 5 - FS/t/cust_bill_pkg_display.t | 5 - FS/t/cust_bill_pkg_tax_location.t | 5 - FS/t/cust_bill_pkg_tax_rate_location.t | 5 - FS/t/cust_category.t | 5 - FS/t/cust_class.t | 5 - FS/t/cust_credit.t | 5 - FS/t/cust_credit_bill.t | 5 - FS/t/cust_credit_bill_pkg.t | 5 - FS/t/cust_credit_refund.t | 5 - FS/t/cust_event.t | 5 - FS/t/cust_location.t | 5 - FS/t/cust_main.t | 5 - FS/t/cust_main_Mixin.t | 5 - FS/t/cust_main_county.t | 5 - FS/t/cust_main_exemption.t | 5 - FS/t/cust_main_invoice.t | 5 - FS/t/cust_main_note.t | 5 - FS/t/cust_note_class.t | 5 - FS/t/cust_pay.t | 5 - FS/t/cust_pay_batch.t | 5 - FS/t/cust_pay_pending.t | 5 - FS/t/cust_pay_refund.t | 5 - FS/t/cust_pay_void.t | 5 - FS/t/cust_pkg.t | 5 - FS/t/cust_pkg_detail.t | 5 - FS/t/cust_pkg_discount.t | 5 - FS/t/cust_pkg_option.t | 5 - FS/t/cust_pkg_reason.t | 5 - FS/t/cust_recon.t | 5 - FS/t/cust_refund.t | 5 - FS/t/cust_statement.t | 5 - FS/t/cust_svc.t | 5 - FS/t/cust_svc_option.t | 5 - FS/t/cust_tag.t | 5 - FS/t/cust_tax_adjustment.t | 5 - FS/t/cust_tax_exempt.t | 5 - FS/t/cust_tax_exempt_pkg.t | 5 - FS/t/cust_tax_location.t | 5 - FS/t/discount.t | 5 - FS/t/domain_record.t | 5 - FS/t/dsl_note.t | 5 - FS/t/export_device.t | 5 - FS/t/export_svc.t | 5 - FS/t/h_Common.t | 5 - FS/t/h_cust_bill.t | 5 - FS/t/h_cust_credit.t | 5 - FS/t/h_cust_pay.t | 5 - FS/t/h_cust_pkg.t | 5 - FS/t/h_cust_pkg_reason.t | 5 - FS/t/h_cust_svc.t | 5 - FS/t/h_cust_tax_exempt.t | 5 - FS/t/h_domain_record.t | 5 - FS/t/h_svc_acct.t | 5 - FS/t/h_svc_broadband.t | 5 - FS/t/h_svc_domain.t | 5 - FS/t/h_svc_external.t | 5 - FS/t/h_svc_forward.t | 5 - FS/t/h_svc_mailinglist.t | 5 - FS/t/h_svc_pbx.t | 5 - FS/t/h_svc_www.t | 5 - FS/t/inventory_class.t | 5 - FS/t/inventory_item.t | 5 - FS/t/location_Mixin.t | 5 - FS/t/mailinglist.t | 5 - FS/t/mailinglistmember.t | 5 - FS/t/msg_template.t | 5 - FS/t/msgcat.t | 5 - FS/t/nas.t | 5 - FS/t/option_Common.t | 5 - FS/t/part_bill_event.t | 5 - FS/t/part_device.t | 5 - FS/t/part_event-Action.t | 5 - FS/t/part_event-Condition.t | 5 - FS/t/part_event.t | 5 - FS/t/part_event_condition.t | 5 - FS/t/part_event_condition_option.t | 5 - FS/t/part_event_condition_option_option.t | 5 - FS/t/part_event_option.t | 5 - FS/t/part_export-acct_sql.t | 5 - FS/t/part_export-apache.t | 5 - FS/t/part_export-bind.t | 5 - FS/t/part_export-bind_slave.t | 5 - FS/t/part_export-bsdshell.t | 5 - FS/t/part_export-communigate_pro.t | 5 - FS/t/part_export-communigate_pro_singledomain.t | 5 - FS/t/part_export-cp.t | 5 - FS/t/part_export-cyrus.t | 5 - FS/t/part_export-domain_shellcommands.t | 5 - FS/t/part_export-forward_shellcommands.t | 5 - FS/t/part_export-http.t | 5 - FS/t/part_export-infostreet.t | 5 - FS/t/part_export-ldap.t | 5 - FS/t/part_export-null.t | 5 - FS/t/part_export-passwdfile.t | 5 - FS/t/part_export-postfix.t | 5 - FS/t/part_export-radiator.t | 5 - FS/t/part_export-router.t | 5 - FS/t/part_export-shellcommands.t | 5 - FS/t/part_export-shellcommands_withdomain.t | 5 - FS/t/part_export-sqlmail.t | 5 - FS/t/part_export-sqlradius.t | 5 - FS/t/part_export-sqlradius_withdomain.t | 5 - FS/t/part_export-sysvshell.t | 5 - FS/t/part_export-textradius.t | 5 - FS/t/part_export-vpopmail.t | 5 - FS/t/part_export-www_shellcommands.t | 5 - FS/t/part_export.t | 5 - FS/t/part_export_option.t | 5 - FS/t/part_pkg-flat.t | 5 - FS/t/part_pkg-flat_comission.t | 5 - FS/t/part_pkg-flat_comission_cust.t | 5 - FS/t/part_pkg-flat_comission_pkg.t | 5 - FS/t/part_pkg-flat_delayed.t | 5 - FS/t/part_pkg-prorate.t | 5 - FS/t/part_pkg-sesmon_hour.t | 5 - FS/t/part_pkg-sesmon_minute.t | 5 - FS/t/part_pkg-sql_external.t | 5 - FS/t/part_pkg-sql_generic.t | 5 - FS/t/part_pkg-sqlradacct_hour.t | 5 - FS/t/part_pkg-subscription.t | 5 - FS/t/part_pkg-voip_cdr.t | 5 - FS/t/part_pkg-voip_sqlradacct.t | 5 - FS/t/part_pkg.t | 5 - FS/t/part_pkg_discount.t | 5 - FS/t/part_pkg_link.t | 5 - FS/t/part_pkg_option.t | 5 - FS/t/part_pkg_report_option.t | 5 - FS/t/part_pkg_taxclass.t | 5 - FS/t/part_pkg_taxoverride.t | 5 - FS/t/part_pkg_taxproduct.t | 5 - FS/t/part_pkg_taxrate.t | 5 - FS/t/part_pkg_vendor.t | 5 - FS/t/part_pop_local.t | 5 - FS/t/part_referral.t | 5 - FS/t/part_svc.t | 5 - FS/t/part_svc_column.t | 5 - FS/t/part_tag.t | 5 - FS/t/pay_batch.t | 5 - FS/t/payby.t | 5 - FS/t/payinfo_Mixin.t | 5 - FS/t/payment_gateway.t | 5 - FS/t/payment_gateway_option.t | 5 - FS/t/phone_avail.t | 5 - FS/t/phone_device.t | 5 - FS/t/phone_type.t | 5 - FS/t/pkg_category.t | 5 - FS/t/pkg_class.t | 5 - FS/t/pkg_referral.t | 5 - FS/t/pkg_svc.t | 5 - FS/t/port.t | 5 - FS/t/prepay_credit.t | 5 - FS/t/prospect_main.t | 5 - FS/t/qual.t | 5 - FS/t/qual_option.t | 5 - FS/t/queue.t | 5 - FS/t/queue_arg.t | 5 - FS/t/queue_depend.t | 5 - FS/t/raddb.t | 5 - FS/t/radius_usergroup.t | 5 - FS/t/rate.t | 5 - FS/t/rate_detail.t | 5 - FS/t/rate_prefix.t | 5 - FS/t/rate_region.t | 5 - FS/t/rate_time.t | 5 - FS/t/rate_time_interval.t | 5 - FS/t/reason.t | 5 - FS/t/reason_type.t | 5 - FS/t/reg_code.t | 5 - FS/t/reg_code_pkg.t | 5 - FS/t/registrar.t | 5 - FS/t/router.t | 5 - FS/t/session.t | 5 - FS/t/svc_CGPRule_Mixin.t | 5 - FS/t/svc_Common.t | 5 - FS/t/svc_Domain_Mixin.t | 5 - FS/t/svc_External_Common.t | 5 - FS/t/svc_Parent_Mixin.t | 5 - FS/t/svc_acct.t | 5 - FS/t/svc_acct_pop.t | 5 - FS/t/svc_broadband.t | 5 - FS/t/svc_cert.t | 5 - FS/t/svc_domain.t | 5 - FS/t/svc_dsl.t | 5 - FS/t/svc_external.t | 5 - FS/t/svc_forward.t | 5 - FS/t/svc_mailinglist.t | 5 - FS/t/svc_pbx.t | 5 - FS/t/svc_phone.t | 5 - FS/t/svc_www.t | 5 - FS/t/tax_class.t | 5 - FS/t/tax_rate.t | 5 - FS/t/tax_rate_location.t | 5 - FS/t/type_pkgs.t | 5 - FS/t/usage_class.t | 5 - 774 files changed, 133140 deletions(-) delete mode 100644 FS/Changes delete mode 100644 FS/FS.pm delete mode 100644 FS/FS/AccessRight.pm delete mode 100644 FS/FS/CGI.pm delete mode 100644 FS/FS/ClientAPI.pm delete mode 100644 FS/FS/ClientAPI/Agent.pm delete mode 100644 FS/FS/ClientAPI/Bulk.pm delete mode 100644 FS/FS/ClientAPI/MasonComponent.pm delete mode 100644 FS/FS/ClientAPI/MyAccount.pm delete mode 100644 FS/FS/ClientAPI/PrepaidPhone.pm delete mode 100644 FS/FS/ClientAPI/SGNG.pm delete mode 100644 FS/FS/ClientAPI/Signup.pm delete mode 100644 FS/FS/ClientAPI/passwd.pm delete mode 100644 FS/FS/ClientAPI_SessionCache.pm delete mode 100644 FS/FS/ClientAPI_XMLRPC.pm delete mode 100644 FS/FS/Conf.pm delete mode 100644 FS/FS/ConfDefaults.pm delete mode 100644 FS/FS/ConfItem.pm delete mode 100644 FS/FS/Conf_compat17.pm delete mode 100644 FS/FS/Cron/alert_expiration.pm delete mode 100644 FS/FS/Cron/backup.pm delete mode 100644 FS/FS/Cron/bill.pm delete mode 100644 FS/FS/Cron/breakage.pm delete mode 100644 FS/FS/Cron/check.pm delete mode 100644 FS/FS/Cron/expire_user_pref.pm delete mode 100644 FS/FS/Cron/notify.pm delete mode 100644 FS/FS/Cron/upload.pm delete mode 100644 FS/FS/Cron/vacuum.pm delete mode 100644 FS/FS/CurrentUser.pm delete mode 100644 FS/FS/Daemon.pm delete mode 100644 FS/FS/InitHandler.pm delete mode 100644 FS/FS/Maestro.pm delete mode 100644 FS/FS/Mason.pm delete mode 100644 FS/FS/Mason/Request.pm delete mode 100644 FS/FS/Misc.pm delete mode 100644 FS/FS/Misc/DateTime.pm delete mode 100644 FS/FS/Misc/eps2png.pm delete mode 100644 FS/FS/Misc/prune.pm delete mode 100644 FS/FS/Msgcat.pm delete mode 100644 FS/FS/Pony.pm delete mode 100644 FS/FS/Record.pm delete mode 100644 FS/FS/Report.pm delete mode 100644 FS/FS/Report/FCC_477.pm delete mode 100644 FS/FS/Report/Table.pm delete mode 100644 FS/FS/Report/Table/Monthly.pm delete mode 100644 FS/FS/Schema.pm delete mode 100644 FS/FS/SearchCache.pm delete mode 100644 FS/FS/Setup.pm delete mode 100644 FS/FS/TicketSystem.pm delete mode 100644 FS/FS/TicketSystem/RT_External.pm delete mode 100644 FS/FS/TicketSystem/RT_Internal.pm delete mode 100644 FS/FS/TicketSystem/RT_Libs.pm delete mode 100644 FS/FS/Tron.pm delete mode 100644 FS/FS/UI/Web.pm delete mode 100644 FS/FS/UI/Web/small_custview.pm delete mode 100644 FS/FS/UI/bytecount.pm delete mode 100644 FS/FS/UID.pm delete mode 100644 FS/FS/Upgrade.pm delete mode 100644 FS/FS/XMLRPC.pm delete mode 100644 FS/FS/Yori.pm delete mode 100644 FS/FS/access_group.pm delete mode 100644 FS/FS/access_groupagent.pm delete mode 100644 FS/FS/access_right.pm delete mode 100644 FS/FS/access_user.pm delete mode 100644 FS/FS/access_user_pref.pm delete mode 100644 FS/FS/access_usergroup.pm delete mode 100644 FS/FS/acct_rt_transaction.pm delete mode 100644 FS/FS/acct_snarf.pm delete mode 100755 FS/FS/addr_block.pm delete mode 100644 FS/FS/agent.pm delete mode 100644 FS/FS/agent_payment_gateway.pm delete mode 100644 FS/FS/agent_type.pm delete mode 100644 FS/FS/banned_pay.pm delete mode 100644 FS/FS/bill_batch.pm delete mode 100644 FS/FS/category_Common.pm delete mode 100644 FS/FS/cdr.pm delete mode 100644 FS/FS/cdr/asterisk.pm delete mode 100644 FS/FS/cdr/bell_west.pm delete mode 100644 FS/FS/cdr/broadsoft.pm delete mode 100644 FS/FS/cdr/cia.pm delete mode 100644 FS/FS/cdr/genband.pm delete mode 100644 FS/FS/cdr/genband_meetme.pm delete mode 100644 FS/FS/cdr/indosoft.pm delete mode 100644 FS/FS/cdr/infinite.pm delete mode 100644 FS/FS/cdr/netcentrex.pm delete mode 100644 FS/FS/cdr/nextone.pm delete mode 100644 FS/FS/cdr/openser.pm delete mode 100644 FS/FS/cdr/sansay.pm delete mode 100644 FS/FS/cdr/simple.pm delete mode 100644 FS/FS/cdr/simple2.pm delete mode 100644 FS/FS/cdr/taqua.pm delete mode 100644 FS/FS/cdr/taqua_om.pm delete mode 100644 FS/FS/cdr/telos_csv.pm delete mode 100644 FS/FS/cdr/telos_xml.pm delete mode 100644 FS/FS/cdr/transnexus.pm delete mode 100644 FS/FS/cdr/troop.pm delete mode 100644 FS/FS/cdr/unitel.pm delete mode 100644 FS/FS/cdr/vitelity.pm delete mode 100644 FS/FS/cdr/wip.pm delete mode 100644 FS/FS/cdr_batch.pm delete mode 100644 FS/FS/cdr_calltype.pm delete mode 100644 FS/FS/cdr_carrier.pm delete mode 100644 FS/FS/cdr_termination.pm delete mode 100644 FS/FS/cdr_type.pm delete mode 100644 FS/FS/cgp_rule.pm delete mode 100644 FS/FS/cgp_rule_action.pm delete mode 100644 FS/FS/cgp_rule_condition.pm delete mode 100644 FS/FS/class_Common.pm delete mode 100644 FS/FS/clientapi_session.pm delete mode 100644 FS/FS/clientapi_session_field.pm delete mode 100644 FS/FS/conf.pm delete mode 100644 FS/FS/contact.pm delete mode 100644 FS/FS/contact_email.pm delete mode 100644 FS/FS/contact_phone.pm delete mode 100644 FS/FS/cust_attachment.pm delete mode 100644 FS/FS/cust_bill.pm delete mode 100644 FS/FS/cust_bill_ApplicationCommon.pm delete mode 100644 FS/FS/cust_bill_batch.pm delete mode 100644 FS/FS/cust_bill_batch_option.pm delete mode 100644 FS/FS/cust_bill_event.pm delete mode 100644 FS/FS/cust_bill_pay.pm delete mode 100644 FS/FS/cust_bill_pay_batch.pm delete mode 100644 FS/FS/cust_bill_pay_pkg.pm delete mode 100644 FS/FS/cust_bill_pkg.pm delete mode 100644 FS/FS/cust_bill_pkg_detail.pm delete mode 100644 FS/FS/cust_bill_pkg_discount.pm delete mode 100644 FS/FS/cust_bill_pkg_display.pm delete mode 100644 FS/FS/cust_bill_pkg_tax_location.pm delete mode 100644 FS/FS/cust_bill_pkg_tax_rate_location.pm delete mode 100644 FS/FS/cust_category.pm delete mode 100644 FS/FS/cust_class.pm delete mode 100644 FS/FS/cust_credit.pm delete mode 100644 FS/FS/cust_credit_bill.pm delete mode 100644 FS/FS/cust_credit_bill_pkg.pm delete mode 100644 FS/FS/cust_credit_refund.pm delete mode 100644 FS/FS/cust_event.pm delete mode 100644 FS/FS/cust_location.pm delete mode 100644 FS/FS/cust_main.pm delete mode 100644 FS/FS/cust_main/Billing.pm delete mode 100644 FS/FS/cust_main/Billing_Discount.pm delete mode 100644 FS/FS/cust_main/Billing_Realtime.pm delete mode 100644 FS/FS/cust_main/Import.pm delete mode 100644 FS/FS/cust_main/Packages.pm delete mode 100644 FS/FS/cust_main/Search.pm delete mode 100644 FS/FS/cust_main/_Marketgear.pm delete mode 100644 FS/FS/cust_main_Mixin.pm delete mode 100644 FS/FS/cust_main_county.pm delete mode 100644 FS/FS/cust_main_exemption.pm delete mode 100644 FS/FS/cust_main_invoice.pm delete mode 100644 FS/FS/cust_main_note.pm delete mode 100644 FS/FS/cust_note_class.pm delete mode 100644 FS/FS/cust_pay.pm delete mode 100644 FS/FS/cust_pay_batch.pm delete mode 100644 FS/FS/cust_pay_pending.pm delete mode 100644 FS/FS/cust_pay_refund.pm delete mode 100644 FS/FS/cust_pay_void.pm delete mode 100644 FS/FS/cust_pkg.pm delete mode 100644 FS/FS/cust_pkg/Import.pm delete mode 100644 FS/FS/cust_pkg_detail.pm delete mode 100644 FS/FS/cust_pkg_discount.pm delete mode 100644 FS/FS/cust_pkg_option.pm delete mode 100644 FS/FS/cust_pkg_reason.pm delete mode 100644 FS/FS/cust_recon.pm delete mode 100644 FS/FS/cust_refund.pm delete mode 100644 FS/FS/cust_statement.pm delete mode 100644 FS/FS/cust_svc.pm delete mode 100644 FS/FS/cust_svc_option.pm delete mode 100644 FS/FS/cust_tag.pm delete mode 100644 FS/FS/cust_tax_adjustment.pm delete mode 100644 FS/FS/cust_tax_exempt.pm delete mode 100644 FS/FS/cust_tax_exempt_pkg.pm delete mode 100644 FS/FS/cust_tax_location.pm delete mode 100644 FS/FS/discount.pm delete mode 100644 FS/FS/domain_record.pm delete mode 100644 FS/FS/dsl_note.pm delete mode 100644 FS/FS/export_device.pm delete mode 100644 FS/FS/export_svc.pm delete mode 100644 FS/FS/geocode_Mixin.pm delete mode 100644 FS/FS/h_Common.pm delete mode 100644 FS/FS/h_cust_bill.pm delete mode 100644 FS/FS/h_cust_credit.pm delete mode 100644 FS/FS/h_cust_pay.pm delete mode 100644 FS/FS/h_cust_pkg.pm delete mode 100644 FS/FS/h_cust_pkg_reason.pm delete mode 100644 FS/FS/h_cust_svc.pm delete mode 100644 FS/FS/h_cust_tax_exempt.pm delete mode 100644 FS/FS/h_domain_record.pm delete mode 100644 FS/FS/h_inventory_item.pm delete mode 100644 FS/FS/h_svc_acct.pm delete mode 100644 FS/FS/h_svc_broadband.pm delete mode 100644 FS/FS/h_svc_domain.pm delete mode 100644 FS/FS/h_svc_dsl.pm delete mode 100644 FS/FS/h_svc_external.pm delete mode 100644 FS/FS/h_svc_forward.pm delete mode 100644 FS/FS/h_svc_mailinglist.pm delete mode 100644 FS/FS/h_svc_pbx.pm delete mode 100644 FS/FS/h_svc_phone.pm delete mode 100644 FS/FS/h_svc_www.pm delete mode 100644 FS/FS/inventory_class.pm delete mode 100644 FS/FS/inventory_item.pm delete mode 100644 FS/FS/location_Mixin.pm delete mode 100644 FS/FS/m2m_Common.pm delete mode 100644 FS/FS/m2name_Common.pm delete mode 100644 FS/FS/mailinglist.pm delete mode 100644 FS/FS/mailinglistmember.pm delete mode 100644 FS/FS/msg_template.pm delete mode 100644 FS/FS/msgcat.pm delete mode 100644 FS/FS/nas.pm delete mode 100644 FS/FS/o2m_Common.pm delete mode 100644 FS/FS/option_Common.pm delete mode 100644 FS/FS/otaker_Mixin.pm delete mode 100644 FS/FS/part_bill_event.pm delete mode 100644 FS/FS/part_device.pm delete mode 100644 FS/FS/part_event.pm delete mode 100644 FS/FS/part_event/Action.pm delete mode 100644 FS/FS/part_event/Action/Mixin/credit_pkg.pm delete mode 100644 FS/FS/part_event/Action/addpost.pm delete mode 100644 FS/FS/part_event/Action/apply.pm delete mode 100644 FS/FS/part_event/Action/bill.pm delete mode 100644 FS/FS/part_event/Action/cancel.pm delete mode 100644 FS/FS/part_event/Action/collect.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_batch.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_comp.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_email.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_fee_percent.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_realtime_card.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_realtime_check.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_realtime_lec.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_send.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_send_agent.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_send_alternate.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_send_if_newest.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_send_reminder.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_spool_csv.pm delete mode 100644 FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm delete mode 100644 FS/FS/part_event/Action/cust_statement.pm delete mode 100644 FS/FS/part_event/Action/cust_statement_send.pm delete mode 100644 FS/FS/part_event/Action/fee.pm delete mode 100644 FS/FS/part_event/Action/notice.pm delete mode 100644 FS/FS/part_event/Action/notice_to.pm delete mode 100644 FS/FS/part_event/Action/pkg_agent_credit.pm delete mode 100644 FS/FS/part_event/Action/pkg_agent_credit_pkg.pm delete mode 100644 FS/FS/part_event/Action/pkg_cancel.pm delete mode 100644 FS/FS/part_event/Action/pkg_employee_credit.pm delete mode 100644 FS/FS/part_event/Action/pkg_employee_credit_pkg.pm delete mode 100644 FS/FS/part_event/Action/pkg_referral_credit.pm delete mode 100644 FS/FS/part_event/Action/pkg_referral_credit_pkg.pm delete mode 100644 FS/FS/part_event/Action/suspend.pm delete mode 100644 FS/FS/part_event/Action/suspend_if_pkgpart.pm delete mode 100644 FS/FS/part_event/Action/suspend_unless_pkgpart.pm delete mode 100644 FS/FS/part_event/Action/writeoff.pm delete mode 100644 FS/FS/part_event/Condition.pm delete mode 100644 FS/FS/part_event/Condition/agent.pm delete mode 100644 FS/FS/part_event/Condition/agent_type.pm delete mode 100644 FS/FS/part_event/Condition/balance.pm delete mode 100644 FS/FS/part_event/Condition/balance_age.pm delete mode 100644 FS/FS/part_event/Condition/balance_credit_limit.pm delete mode 100644 FS/FS/part_event/Condition/balance_under.pm delete mode 100644 FS/FS/part_event/Condition/cust_bill_age.pm delete mode 100644 FS/FS/part_event/Condition/cust_bill_has_noauto.pm delete mode 100644 FS/FS/part_event/Condition/cust_bill_has_service.pm delete mode 100644 FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm delete mode 100644 FS/FS/part_event/Condition/cust_bill_owed.pm delete mode 100644 FS/FS/part_event/Condition/cust_bill_owed_under.pm delete mode 100644 FS/FS/part_event/Condition/cust_bill_past_due.pm delete mode 100644 FS/FS/part_event/Condition/cust_pay_batch_declined.pm delete mode 100644 FS/FS/part_event/Condition/cust_payments.pm delete mode 100644 FS/FS/part_event/Condition/cust_payments_pkg.pm delete mode 100644 FS/FS/part_event/Condition/cust_status.pm delete mode 100644 FS/FS/part_event/Condition/dundate.pm delete mode 100644 FS/FS/part_event/Condition/every.pm delete mode 100644 FS/FS/part_event/Condition/has_pkg_class.pm delete mode 100644 FS/FS/part_event/Condition/has_pkgpart.pm delete mode 100644 FS/FS/part_event/Condition/has_referral_custnum.pm delete mode 100644 FS/FS/part_event/Condition/hasnt_pkgpart.pm delete mode 100644 FS/FS/part_event/Condition/once.pm delete mode 100644 FS/FS/part_event/Condition/once_every.pm delete mode 100644 FS/FS/part_event/Condition/once_percust.pm delete mode 100644 FS/FS/part_event/Condition/once_perinv.pm delete mode 100644 FS/FS/part_event/Condition/payby.pm delete mode 100644 FS/FS/part_event/Condition/pkg_age.pm delete mode 100644 FS/FS/part_event/Condition/pkg_class.pm delete mode 100644 FS/FS/part_event/Condition/pkg_freq.pm delete mode 100644 FS/FS/part_event/Condition/pkg_next_bill_within.pm delete mode 100644 FS/FS/part_event/Condition/pkg_notchange.pm delete mode 100644 FS/FS/part_event/Condition/pkg_pkgpart.pm delete mode 100644 FS/FS/part_event/Condition/pkg_recurring.pm delete mode 100644 FS/FS/part_event/Condition/pkg_status.pm delete mode 100644 FS/FS/part_event/Condition/pkg_unless_pkgpart.pm delete mode 100644 FS/FS/part_event_condition.pm delete mode 100644 FS/FS/part_event_condition_option.pm delete mode 100644 FS/FS/part_event_condition_option_option.pm delete mode 100644 FS/FS/part_event_option.pm delete mode 100644 FS/FS/part_export.pm delete mode 100644 FS/FS/part_export/acct_freeside.pm delete mode 100644 FS/FS/part_export/acct_http.pm delete mode 100644 FS/FS/part_export/acct_plesk.pm delete mode 100644 FS/FS/part_export/acct_sql.pm delete mode 100644 FS/FS/part_export/amazon_ec2.pm delete mode 100644 FS/FS/part_export/apache.pm delete mode 100644 FS/FS/part_export/artera_turbo.pm delete mode 100644 FS/FS/part_export/bind.pm delete mode 100644 FS/FS/part_export/bind_slave.pm delete mode 100644 FS/FS/part_export/bsdshell.pm delete mode 100644 FS/FS/part_export/cardfortress.pm delete mode 100644 FS/FS/part_export/communigate_pro.pm delete mode 100644 FS/FS/part_export/communigate_pro_singledomain.pm delete mode 100644 FS/FS/part_export/cp.pm delete mode 100644 FS/FS/part_export/cpanel.pm delete mode 100644 FS/FS/part_export/cust_http.pm delete mode 100644 FS/FS/part_export/cyrus.pm delete mode 100644 FS/FS/part_export/dashcs_e911.pm delete mode 100644 FS/FS/part_export/domain_shellcommands.pm delete mode 100644 FS/FS/part_export/domain_sql.pm delete mode 100644 FS/FS/part_export/domreg_net_dri.pm delete mode 100644 FS/FS/part_export/domreg_opensrs.pm delete mode 100644 FS/FS/part_export/everyone_net.pm delete mode 100644 FS/FS/part_export/forward_shellcommands.pm delete mode 100644 FS/FS/part_export/globalpops_voip.pm delete mode 100644 FS/FS/part_export/grandstream.pm delete mode 100644 FS/FS/part_export/http.pm delete mode 100644 FS/FS/part_export/ikano.pm delete mode 100644 FS/FS/part_export/indosoft.pm delete mode 100644 FS/FS/part_export/infostreet.pm delete mode 100644 FS/FS/part_export/internal_diddb.pm delete mode 100644 FS/FS/part_export/ldap.pm delete mode 100644 FS/FS/part_export/nas_wrapper.pm delete mode 100644 FS/FS/part_export/netsapiens.pm delete mode 100644 FS/FS/part_export/null.pm delete mode 100644 FS/FS/part_export/passwdfile.pm delete mode 100644 FS/FS/part_export/phone_shellcommands.pm delete mode 100644 FS/FS/part_export/phone_sqlradius.pm delete mode 100644 FS/FS/part_export/postfix.pm delete mode 100644 FS/FS/part_export/prizm.pm delete mode 100644 FS/FS/part_export/radiator.pm delete mode 100644 FS/FS/part_export/router.pm delete mode 100644 FS/FS/part_export/rt_ticket.pm delete mode 100644 FS/FS/part_export/shellcommands.pm delete mode 100644 FS/FS/part_export/shellcommands_withdomain.pm delete mode 100644 FS/FS/part_export/snmp.pm delete mode 100644 FS/FS/part_export/soma.pm delete mode 100644 FS/FS/part_export/sqlmail.pm delete mode 100644 FS/FS/part_export/sqlradius.pm delete mode 100644 FS/FS/part_export/sqlradius_withdomain.pm delete mode 100644 FS/FS/part_export/sysvshell.pm delete mode 100644 FS/FS/part_export/textradius.pm delete mode 100644 FS/FS/part_export/thirdlane.pm delete mode 100644 FS/FS/part_export/trango.pm delete mode 100644 FS/FS/part_export/vitelity.pm delete mode 100644 FS/FS/part_export/vpopmail.pm delete mode 100644 FS/FS/part_export/www_plesk.pm delete mode 100644 FS/FS/part_export/www_shellcommands.pm delete mode 100644 FS/FS/part_export_option.pm delete mode 100644 FS/FS/part_pkg.pm delete mode 100644 FS/FS/part_pkg/agent.pm delete mode 100644 FS/FS/part_pkg/base_delayed.pm delete mode 100644 FS/FS/part_pkg/base_rate.pm delete mode 100644 FS/FS/part_pkg/bulk.pm delete mode 100644 FS/FS/part_pkg/cdr_termination.pm delete mode 100644 FS/FS/part_pkg/discount_Mixin.pm delete mode 100644 FS/FS/part_pkg/flat.pm delete mode 100644 FS/FS/part_pkg/flat_comission.pm delete mode 100644 FS/FS/part_pkg/flat_comission_cust.pm delete mode 100644 FS/FS/part_pkg/flat_comission_pkg.pm delete mode 100644 FS/FS/part_pkg/flat_delayed.pm delete mode 100644 FS/FS/part_pkg/flat_introrate.pm delete mode 100644 FS/FS/part_pkg/global_Mixin.pm delete mode 100644 FS/FS/part_pkg/incomplete/billoneday.pm delete mode 100644 FS/FS/part_pkg/prepaid.pm delete mode 100644 FS/FS/part_pkg/prorate.pm delete mode 100644 FS/FS/part_pkg/prorate_Mixin.pm delete mode 100644 FS/FS/part_pkg/prorate_delayed.pm delete mode 100644 FS/FS/part_pkg/recur_Common.pm delete mode 100644 FS/FS/part_pkg/rt_time.pm delete mode 100644 FS/FS/part_pkg/sesmon_hour.pm delete mode 100644 FS/FS/part_pkg/sesmon_minute.pm delete mode 100644 FS/FS/part_pkg/sql_external.pm delete mode 100644 FS/FS/part_pkg/sql_generic.pm delete mode 100644 FS/FS/part_pkg/sqlradacct_hour.pm delete mode 100644 FS/FS/part_pkg/subscription.pm delete mode 100644 FS/FS/part_pkg/usage_Mixin.pm delete mode 100644 FS/FS/part_pkg/voip_cdr.pm delete mode 100644 FS/FS/part_pkg/voip_inbound.pm delete mode 100644 FS/FS/part_pkg/voip_sqlradacct.pm delete mode 100644 FS/FS/part_pkg_discount.pm delete mode 100644 FS/FS/part_pkg_link.pm delete mode 100644 FS/FS/part_pkg_option.pm delete mode 100644 FS/FS/part_pkg_report_option.pm delete mode 100644 FS/FS/part_pkg_taxclass.pm delete mode 100644 FS/FS/part_pkg_taxoverride.pm delete mode 100644 FS/FS/part_pkg_taxproduct.pm delete mode 100644 FS/FS/part_pkg_taxrate.pm delete mode 100644 FS/FS/part_pkg_vendor.pm delete mode 100644 FS/FS/part_pop_local.pm delete mode 100644 FS/FS/part_referral.pm delete mode 100644 FS/FS/part_svc.pm delete mode 100644 FS/FS/part_svc_column.pm delete mode 100755 FS/FS/part_svc_router.pm delete mode 100644 FS/FS/part_tag.pm delete mode 100755 FS/FS/part_virtual_field.pm delete mode 100644 FS/FS/pay_batch.pm delete mode 100644 FS/FS/pay_batch/BoM.pm delete mode 100644 FS/FS/pay_batch/PAP.pm delete mode 100644 FS/FS/pay_batch/RBC.pm delete mode 100644 FS/FS/pay_batch/ach_spiritone.pm delete mode 100644 FS/FS/pay_batch/chase_canada.pm delete mode 100644 FS/FS/pay_batch/paymentech.pm delete mode 100644 FS/FS/pay_batch/td_canada_trust.pm delete mode 100644 FS/FS/pay_batch/td_eft1464.pm delete mode 100644 FS/FS/pay_batch/td_eftack264.pm delete mode 100644 FS/FS/pay_batch/td_eftret80.pm delete mode 100644 FS/FS/payby.pm delete mode 100644 FS/FS/payinfo_Mixin.pm delete mode 100644 FS/FS/payinfo_transaction_Mixin.pm delete mode 100644 FS/FS/payment_gateway.pm delete mode 100644 FS/FS/payment_gateway_option.pm delete mode 100644 FS/FS/phone_avail.pm delete mode 100644 FS/FS/phone_device.pm delete mode 100644 FS/FS/phone_type.pm delete mode 100644 FS/FS/pkg_category.pm delete mode 100644 FS/FS/pkg_class.pm delete mode 100644 FS/FS/pkg_referral.pm delete mode 100644 FS/FS/pkg_svc.pm delete mode 100644 FS/FS/port.pm delete mode 100644 FS/FS/prepay_credit.pm delete mode 100644 FS/FS/prospect_main.pm delete mode 100644 FS/FS/qual.pm delete mode 100644 FS/FS/qual_option.pm delete mode 100644 FS/FS/queue.pm delete mode 100644 FS/FS/queue_arg.pm delete mode 100644 FS/FS/queue_depend.pm delete mode 100644 FS/FS/raddb.pm delete mode 100644 FS/FS/radius_usergroup.pm delete mode 100644 FS/FS/rate.pm delete mode 100644 FS/FS/rate_detail.pm delete mode 100644 FS/FS/rate_prefix.pm delete mode 100644 FS/FS/rate_region.pm delete mode 100644 FS/FS/rate_time.pm delete mode 100644 FS/FS/rate_time_interval.pm delete mode 100644 FS/FS/reason.pm delete mode 100644 FS/FS/reason_type.pm delete mode 100644 FS/FS/reg_code.pm delete mode 100644 FS/FS/reg_code_pkg.pm delete mode 100644 FS/FS/registrar.pm delete mode 100755 FS/FS/router.pm delete mode 100644 FS/FS/session.pm delete mode 100644 FS/FS/svc_CGPRule_Mixin.pm delete mode 100644 FS/FS/svc_CGP_Mixin.pm delete mode 100644 FS/FS/svc_Common.pm delete mode 100644 FS/FS/svc_Domain_Mixin.pm delete mode 100644 FS/FS/svc_External_Common.pm delete mode 100644 FS/FS/svc_Parent_Mixin.pm delete mode 100644 FS/FS/svc_acct.pm delete mode 100644 FS/FS/svc_acct_pop.pm delete mode 100755 FS/FS/svc_broadband.pm delete mode 100644 FS/FS/svc_cert.pm delete mode 100644 FS/FS/svc_domain.pm delete mode 100644 FS/FS/svc_dsl.pm delete mode 100644 FS/FS/svc_external.pm delete mode 100644 FS/FS/svc_forward.pm delete mode 100644 FS/FS/svc_mailinglist.pm delete mode 100644 FS/FS/svc_pbx.pm delete mode 100644 FS/FS/svc_phone.pm delete mode 100644 FS/FS/svc_www.pm delete mode 100644 FS/FS/tax_class.pm delete mode 100644 FS/FS/tax_rate.pm delete mode 100644 FS/FS/tax_rate_location.pm delete mode 100644 FS/FS/type_pkgs.pm delete mode 100644 FS/FS/usage_class.pm delete mode 100644 FS/MANIFEST delete mode 100644 FS/MANIFEST.SKIP delete mode 100644 FS/Makefile.PL delete mode 100755 FS/bin/freeside-addgroup delete mode 100644 FS/bin/freeside-addoutsource delete mode 100644 FS/bin/freeside-addoutsourceuser delete mode 100644 FS/bin/freeside-adduser delete mode 100755 FS/bin/freeside-apply-credits delete mode 100755 FS/bin/freeside-apply_payments_and_credits delete mode 100755 FS/bin/freeside-cdr-sftp_and_import delete mode 100644 FS/bin/freeside-cdrd delete mode 100644 FS/bin/freeside-cdrrewrited delete mode 100644 FS/bin/freeside-check delete mode 100755 FS/bin/freeside-count-active-customers delete mode 100755 FS/bin/freeside-daily delete mode 100755 FS/bin/freeside-dbdef-create delete mode 100755 FS/bin/freeside-dedup-cust_bill_pkg_detail-header delete mode 100755 FS/bin/freeside-delete-addr_blocks delete mode 100644 FS/bin/freeside-deloutsource delete mode 100644 FS/bin/freeside-deloutsourceuser delete mode 100644 FS/bin/freeside-deluser delete mode 100755 FS/bin/freeside-disable-reasons delete mode 100755 FS/bin/freeside-email delete mode 100755 FS/bin/freeside-fetch delete mode 100755 FS/bin/freeside-history-requeue delete mode 100755 FS/bin/freeside-init-config delete mode 100755 FS/bin/freeside-monthly delete mode 100755 FS/bin/freeside-paymentech-download delete mode 100755 FS/bin/freeside-paymentech-upload delete mode 100644 FS/bin/freeside-prepaidd delete mode 100755 FS/bin/freeside-prune-applications delete mode 100755 FS/bin/freeside-pull-dsl delete mode 100644 FS/bin/freeside-queued delete mode 100644 FS/bin/freeside-radgroup delete mode 100644 FS/bin/freeside-reexport delete mode 100755 FS/bin/freeside-reset-fixed delete mode 100644 FS/bin/freeside-selfservice-server delete mode 100755 FS/bin/freeside-selfservice-xmlrpcd delete mode 100644 FS/bin/freeside-setinvoice delete mode 100755 FS/bin/freeside-setup delete mode 100755 FS/bin/freeside-sqlradius-dedup-group delete mode 100644 FS/bin/freeside-sqlradius-radacctd delete mode 100755 FS/bin/freeside-sqlradius-reset delete mode 100644 FS/bin/freeside-sqlradius-seconds delete mode 100755 FS/bin/freeside-sqlradius-set-lastlog delete mode 100755 FS/bin/freeside-upgrade delete mode 100755 FS/bin/freeside-void-payments delete mode 100755 FS/bin/freeside-wipe-cvv delete mode 100644 FS/bin/freeside-yori delete mode 100644 FS/t/AccessRight.t delete mode 100644 FS/t/CGI.t delete mode 100644 FS/t/ClientAPI.t delete mode 100644 FS/t/ClientAPI_SessionCache.t delete mode 100644 FS/t/Conf.t delete mode 100644 FS/t/ConfDefaults.t delete mode 100644 FS/t/ConfItem.t delete mode 100644 FS/t/Cron-backup.t delete mode 100644 FS/t/Cron-bill.t delete mode 100644 FS/t/Cron-vacuum.t delete mode 100644 FS/t/Daemon.t delete mode 100644 FS/t/InitHandler.t delete mode 100644 FS/t/Misc.t delete mode 100644 FS/t/Msgcat.t delete mode 100644 FS/t/Record.t delete mode 100644 FS/t/Report-FCC_477.t delete mode 100644 FS/t/Report-Table-Monthly.t delete mode 100644 FS/t/Report-Table.t delete mode 100644 FS/t/Report.t delete mode 100644 FS/t/SearchCache.t delete mode 100644 FS/t/UID.t delete mode 100644 FS/t/access_group.t delete mode 100644 FS/t/access_groupagent.t delete mode 100644 FS/t/access_right.t delete mode 100644 FS/t/access_user.t delete mode 100644 FS/t/access_user_pref.t delete mode 100644 FS/t/access_usergroup.t delete mode 100644 FS/t/acct_rt_transaction.t delete mode 100644 FS/t/acct_snarf.t delete mode 100644 FS/t/addr_block.t delete mode 100644 FS/t/agent.t delete mode 100644 FS/t/agent_payment_gateway.t delete mode 100644 FS/t/agent_type.t delete mode 100644 FS/t/banned_pay.t delete mode 100644 FS/t/category_Common.t delete mode 100644 FS/t/cdr.t delete mode 100644 FS/t/cdr_batch.t delete mode 100644 FS/t/cdr_calltype.t delete mode 100644 FS/t/cdr_carrier.t delete mode 100644 FS/t/cdr_termination.t delete mode 100644 FS/t/cdr_type.t delete mode 100644 FS/t/cgp_rule.t delete mode 100644 FS/t/cgp_rule_action.t delete mode 100644 FS/t/cgp_rule_condition.t delete mode 100644 FS/t/class_Common.t delete mode 100644 FS/t/clientapi_session.t delete mode 100644 FS/t/clientapi_session_field.t delete mode 100644 FS/t/conf.t delete mode 100644 FS/t/contact.t delete mode 100644 FS/t/contact_email.t delete mode 100644 FS/t/contact_phone.t delete mode 100644 FS/t/cust_attachment.t delete mode 100644 FS/t/cust_bill.t delete mode 100644 FS/t/cust_bill_ApplicationCommon.t delete mode 100644 FS/t/cust_bill_event.t delete mode 100644 FS/t/cust_bill_pay.t delete mode 100644 FS/t/cust_bill_pay_batch.t delete mode 100644 FS/t/cust_bill_pay_pkg.t delete mode 100644 FS/t/cust_bill_pkg.t delete mode 100644 FS/t/cust_bill_pkg_detail.t delete mode 100644 FS/t/cust_bill_pkg_discount.t delete mode 100644 FS/t/cust_bill_pkg_display.t delete mode 100644 FS/t/cust_bill_pkg_tax_location.t delete mode 100644 FS/t/cust_bill_pkg_tax_rate_location.t delete mode 100644 FS/t/cust_category.t delete mode 100644 FS/t/cust_class.t delete mode 100644 FS/t/cust_credit.t delete mode 100644 FS/t/cust_credit_bill.t delete mode 100644 FS/t/cust_credit_bill_pkg.t delete mode 100644 FS/t/cust_credit_refund.t delete mode 100644 FS/t/cust_event.t delete mode 100644 FS/t/cust_location.t delete mode 100644 FS/t/cust_main.t delete mode 100644 FS/t/cust_main_Mixin.t delete mode 100644 FS/t/cust_main_county.t delete mode 100644 FS/t/cust_main_exemption.t delete mode 100644 FS/t/cust_main_invoice.t delete mode 100644 FS/t/cust_main_note.t delete mode 100644 FS/t/cust_note_class.t delete mode 100644 FS/t/cust_pay.t delete mode 100644 FS/t/cust_pay_batch.t delete mode 100644 FS/t/cust_pay_pending.t delete mode 100644 FS/t/cust_pay_refund.t delete mode 100644 FS/t/cust_pay_void.t delete mode 100644 FS/t/cust_pkg.t delete mode 100644 FS/t/cust_pkg_detail.t delete mode 100644 FS/t/cust_pkg_discount.t delete mode 100644 FS/t/cust_pkg_option.t delete mode 100644 FS/t/cust_pkg_reason.t delete mode 100644 FS/t/cust_recon.t delete mode 100644 FS/t/cust_refund.t delete mode 100644 FS/t/cust_statement.t delete mode 100644 FS/t/cust_svc.t delete mode 100644 FS/t/cust_svc_option.t delete mode 100644 FS/t/cust_tag.t delete mode 100644 FS/t/cust_tax_adjustment.t delete mode 100644 FS/t/cust_tax_exempt.t delete mode 100644 FS/t/cust_tax_exempt_pkg.t delete mode 100644 FS/t/cust_tax_location.t delete mode 100644 FS/t/discount.t delete mode 100644 FS/t/domain_record.t delete mode 100644 FS/t/dsl_note.t delete mode 100644 FS/t/export_device.t delete mode 100644 FS/t/export_svc.t delete mode 100644 FS/t/h_Common.t delete mode 100644 FS/t/h_cust_bill.t delete mode 100644 FS/t/h_cust_credit.t delete mode 100644 FS/t/h_cust_pay.t delete mode 100644 FS/t/h_cust_pkg.t delete mode 100644 FS/t/h_cust_pkg_reason.t delete mode 100644 FS/t/h_cust_svc.t delete mode 100644 FS/t/h_cust_tax_exempt.t delete mode 100644 FS/t/h_domain_record.t delete mode 100644 FS/t/h_svc_acct.t delete mode 100644 FS/t/h_svc_broadband.t delete mode 100644 FS/t/h_svc_domain.t delete mode 100644 FS/t/h_svc_external.t delete mode 100644 FS/t/h_svc_forward.t delete mode 100644 FS/t/h_svc_mailinglist.t delete mode 100644 FS/t/h_svc_pbx.t delete mode 100644 FS/t/h_svc_www.t delete mode 100644 FS/t/inventory_class.t delete mode 100644 FS/t/inventory_item.t delete mode 100644 FS/t/location_Mixin.t delete mode 100644 FS/t/mailinglist.t delete mode 100644 FS/t/mailinglistmember.t delete mode 100644 FS/t/msg_template.t delete mode 100644 FS/t/msgcat.t delete mode 100644 FS/t/nas.t delete mode 100644 FS/t/option_Common.t delete mode 100644 FS/t/part_bill_event.t delete mode 100644 FS/t/part_device.t delete mode 100644 FS/t/part_event-Action.t delete mode 100644 FS/t/part_event-Condition.t delete mode 100644 FS/t/part_event.t delete mode 100644 FS/t/part_event_condition.t delete mode 100644 FS/t/part_event_condition_option.t delete mode 100644 FS/t/part_event_condition_option_option.t delete mode 100644 FS/t/part_event_option.t delete mode 100644 FS/t/part_export-acct_sql.t delete mode 100644 FS/t/part_export-apache.t delete mode 100644 FS/t/part_export-bind.t delete mode 100644 FS/t/part_export-bind_slave.t delete mode 100644 FS/t/part_export-bsdshell.t delete mode 100644 FS/t/part_export-communigate_pro.t delete mode 100644 FS/t/part_export-communigate_pro_singledomain.t delete mode 100644 FS/t/part_export-cp.t delete mode 100644 FS/t/part_export-cyrus.t delete mode 100644 FS/t/part_export-domain_shellcommands.t delete mode 100644 FS/t/part_export-forward_shellcommands.t delete mode 100644 FS/t/part_export-http.t delete mode 100644 FS/t/part_export-infostreet.t delete mode 100644 FS/t/part_export-ldap.t delete mode 100644 FS/t/part_export-null.t delete mode 100644 FS/t/part_export-passwdfile.t delete mode 100644 FS/t/part_export-postfix.t delete mode 100644 FS/t/part_export-radiator.t delete mode 100644 FS/t/part_export-router.t delete mode 100644 FS/t/part_export-shellcommands.t delete mode 100644 FS/t/part_export-shellcommands_withdomain.t delete mode 100644 FS/t/part_export-sqlmail.t delete mode 100644 FS/t/part_export-sqlradius.t delete mode 100644 FS/t/part_export-sqlradius_withdomain.t delete mode 100644 FS/t/part_export-sysvshell.t delete mode 100644 FS/t/part_export-textradius.t delete mode 100644 FS/t/part_export-vpopmail.t delete mode 100644 FS/t/part_export-www_shellcommands.t delete mode 100644 FS/t/part_export.t delete mode 100644 FS/t/part_export_option.t delete mode 100644 FS/t/part_pkg-flat.t delete mode 100644 FS/t/part_pkg-flat_comission.t delete mode 100644 FS/t/part_pkg-flat_comission_cust.t delete mode 100644 FS/t/part_pkg-flat_comission_pkg.t delete mode 100644 FS/t/part_pkg-flat_delayed.t delete mode 100644 FS/t/part_pkg-prorate.t delete mode 100644 FS/t/part_pkg-sesmon_hour.t delete mode 100644 FS/t/part_pkg-sesmon_minute.t delete mode 100644 FS/t/part_pkg-sql_external.t delete mode 100644 FS/t/part_pkg-sql_generic.t delete mode 100644 FS/t/part_pkg-sqlradacct_hour.t delete mode 100644 FS/t/part_pkg-subscription.t delete mode 100644 FS/t/part_pkg-voip_cdr.t delete mode 100644 FS/t/part_pkg-voip_sqlradacct.t delete mode 100644 FS/t/part_pkg.t delete mode 100644 FS/t/part_pkg_discount.t delete mode 100644 FS/t/part_pkg_link.t delete mode 100644 FS/t/part_pkg_option.t delete mode 100644 FS/t/part_pkg_report_option.t delete mode 100644 FS/t/part_pkg_taxclass.t delete mode 100644 FS/t/part_pkg_taxoverride.t delete mode 100644 FS/t/part_pkg_taxproduct.t delete mode 100644 FS/t/part_pkg_taxrate.t delete mode 100644 FS/t/part_pkg_vendor.t delete mode 100644 FS/t/part_pop_local.t delete mode 100644 FS/t/part_referral.t delete mode 100644 FS/t/part_svc.t delete mode 100644 FS/t/part_svc_column.t delete mode 100644 FS/t/part_tag.t delete mode 100644 FS/t/pay_batch.t delete mode 100644 FS/t/payby.t delete mode 100644 FS/t/payinfo_Mixin.t delete mode 100644 FS/t/payment_gateway.t delete mode 100644 FS/t/payment_gateway_option.t delete mode 100644 FS/t/phone_avail.t delete mode 100644 FS/t/phone_device.t delete mode 100644 FS/t/phone_type.t delete mode 100644 FS/t/pkg_category.t delete mode 100644 FS/t/pkg_class.t delete mode 100644 FS/t/pkg_referral.t delete mode 100644 FS/t/pkg_svc.t delete mode 100644 FS/t/port.t delete mode 100644 FS/t/prepay_credit.t delete mode 100644 FS/t/prospect_main.t delete mode 100644 FS/t/qual.t delete mode 100644 FS/t/qual_option.t delete mode 100644 FS/t/queue.t delete mode 100644 FS/t/queue_arg.t delete mode 100644 FS/t/queue_depend.t delete mode 100644 FS/t/raddb.t delete mode 100644 FS/t/radius_usergroup.t delete mode 100644 FS/t/rate.t delete mode 100644 FS/t/rate_detail.t delete mode 100644 FS/t/rate_prefix.t delete mode 100644 FS/t/rate_region.t delete mode 100644 FS/t/rate_time.t delete mode 100644 FS/t/rate_time_interval.t delete mode 100644 FS/t/reason.t delete mode 100644 FS/t/reason_type.t delete mode 100644 FS/t/reg_code.t delete mode 100644 FS/t/reg_code_pkg.t delete mode 100644 FS/t/registrar.t delete mode 100644 FS/t/router.t delete mode 100644 FS/t/session.t delete mode 100644 FS/t/svc_CGPRule_Mixin.t delete mode 100644 FS/t/svc_Common.t delete mode 100644 FS/t/svc_Domain_Mixin.t delete mode 100644 FS/t/svc_External_Common.t delete mode 100644 FS/t/svc_Parent_Mixin.t delete mode 100644 FS/t/svc_acct.t delete mode 100644 FS/t/svc_acct_pop.t delete mode 100644 FS/t/svc_broadband.t delete mode 100644 FS/t/svc_cert.t delete mode 100644 FS/t/svc_domain.t delete mode 100644 FS/t/svc_dsl.t delete mode 100644 FS/t/svc_external.t delete mode 100644 FS/t/svc_forward.t delete mode 100644 FS/t/svc_mailinglist.t delete mode 100644 FS/t/svc_pbx.t delete mode 100644 FS/t/svc_phone.t delete mode 100644 FS/t/svc_www.t delete mode 100644 FS/t/tax_class.t delete mode 100644 FS/t/tax_rate.t delete mode 100644 FS/t/tax_rate_location.t delete mode 100644 FS/t/type_pkgs.t delete mode 100644 FS/t/usage_class.t (limited to 'FS') diff --git a/FS/Changes b/FS/Changes deleted file mode 100644 index c94ef10f5..000000000 --- a/FS/Changes +++ /dev/null @@ -1,5 +0,0 @@ -Revision history for Perl extension FS. - -0.01 Wed Aug 4 00:13:45 1999 - - original version; created by h2xs 1.19 - diff --git a/FS/FS.pm b/FS/FS.pm deleted file mode 100644 index 8e7863940..000000000 --- a/FS/FS.pm +++ /dev/null @@ -1,524 +0,0 @@ -package FS; - -use strict; -use vars qw($VERSION); - -$VERSION = '%%%VERSION%%%'; - -#find missing entries in this file with: -# for a in `ls *pm | cut -d. -f1`; do grep 'L' ../FS.pm >/dev/null || echo "missing $a" ; done - -1; -__END__ - -=head1 NAME - -FS - Freeside Perl modules - -=head1 SYNOPSIS - -Freeside perl modules and CLI utilities. - -=head2 Utility classes - -L - Freeside database schema - -L - Setup subroutines - -L - Upgrade subroutines - -L - Freeside configuration values - -L - Freeside configuration option meta-data. - -L - Freeside configuration default and available values - -L - User class (not yet OO) - -L - Package representing the current user - -L - Non OO-subroutines for the web interface. - -L - Message catalog - -L - Search cache - -L - Access control rights. - -L - Report data objects - -L - Report data objects - -L - Report data objects - -L - Backend XML::RPC server - -L - Miscellaneous subroutines - -L - Payment types - -L - ClientAPI session cache - -L - A pony - -L - Customer searching - -L - Batch customer importing - -=head2 Database record classes - -L - Database record base class - -L - Mixin class for classes in a many-to-many relationship - -L - Base class for tables with a related table listing names - -L - Base class for option sub-classes - -L - Base class for classification classes - -L - Base class for category (grooups of classifications) classes - -L - Configuration value class - -L - Mixin class for records in tables that contain payinfo. - -L - Employees / internal users - -L - Employee preferences - -L - Employee groups - -L - Employee group membership - -L - Group reseller access - -L - Access rights - -L - POP (Point of Presence, not Post -Office Protocol) class - -L - Local calling area class - -L - Referral class - -L - Package referral class - -L - Locale (tax rate) class - -L - Tax exemption record class - -L - Tax adjustment record class - -L - Line-item specific tax exemption record class - -L - Service base class - -L - Mixin class for svc_ classes with a parent_svcnum field - -L - Account (shell, RADIUS, POP3) class - -L - External mail account class - -L - Time worked application to account class - -L - RADIUS groups - -L - Domain class - -L - DNS zone entries - -L - Domain registrar class - -L - Communigate pro rule class - -L - Communigate pro rule condition class - -L - Communigate pro rule action class - -L - Mail forwarding class - -L - (Customer) Mailing list class - -L - Mailing list class - -L - Mailing list member class - -L - Web virtual host class. - -L - DSL, wireless and other broadband class. - -L - DSL - -L - DSL order notes - -L - Address block class - -L - Router class - -L - Broadband virtual field class - -L - Phone service class - -L - Phone device class - -L - Device definition class - -L - Phone number availability cache - -L - Call Detail Record class - -L - Call Detail Record batch class - -L - CDR calltype class - -L - CDR carrier class - -L - CDR type class - -L - Externally tracked service class. - -L - PBX service class - -L - Certificate service class - -L - Inventory classes - -L - Inventory items - -L - Service definition class - -L - Column constraint class - -L - Class linking service definitions (see L) -with exports (see L) - -L - External provisioning export class - -L - Export option class - -L - Package category class (invoice oriented) - -L - Package class class - -L - Package definition class - -L - Package definition link class - -L - Tax class class - -L - Package definition option class - -L - Package reporting classification class - -L - Package external mapping class - -L - Class linking package definitions (see L) with -service definitions (see L) - -L - Service qualification class - -L - Qualification option class - -L - One-time registration codes - -L - Class linking registration codes (see L) with package definitions (see L) - -L - Rate plans for call billing - -L - Rate regions for call billing - -L - Rate region prefixes for call billing - -L - Rate plan detail for call billing - -L - Usage class class - -L - Agent (reseller) class - -L - Agent type class - -L - Class linking agent types (see L) with package definitions (see L) - -L - Payment gateway class - -L - Payment gateway option class - -L - Agent payment gateway class - -L - Service class - -L - Customer package class - -L - Customer package option class - -L - Customer package details class - -L - Customer package discount class - -L - Customer package discount line item application class - -L - Discount class - -L - Reason type class - -L - Reason class - -L - Package reason class - -L - Contact class - -L - Contact phone class - -L - Phone type class - -L - Contact email class - -L - Prospect class - -L - Customer class - -L - Customer billing class - -L - Customer real-time billing class - -L - Customer packages class - -L - Customer location class - -L - Mixin class for records that contain fields from cust_main - -L - Invoice destination class - -L - Customer classification class - -L - Customer category class - -L - Customer tag class - -L - Tag definition class - -L - Customer tax exemption class - -L - Customer note class - -L - Customer note classification class - -L - Banned payment information class - -L - Invoice class - -L - Informational statement class - -L - Invoice line item class - -L - Invoice line item detail class - -L - (Old) Invoice event definition class - -L - (Old) Completed invoice event class - -L - (New) Billing event definition class - -L - (New) Billing event option class - -L - (New) Billing event condition base class - -L - (New) Billing event action base class - -L - (New) Billing event condition class - -L - (New) Billing event condition option class - -L - (New) Billing event condition compound option class - -L - (New) Customer event class - -L - Base class for bill application classes - -L - Payment class - -L - Pending payment class - -L - Voided payment class - -L - Payment application class - -L - Line-item specific payment application class - -L - Batch payment application class - -L - Credit class - -L - Refund class - -L - Refund application to credit class - -L - Credit application to invoice class - -L - Line-item specific credit application to invoice class - -L - Refund application to payment class - -L - Credit card transaction queue class - -L - Credit card transaction member queue class - -L - Prepaid "calling card" credit class. - -L - Network Access Server class - -L - NAS port class - -L - User login session class - -L - Job queue - -L - Job arguments - -L - Job dependencies - -L - Message templates (customer notices) - -L - Message catalogs (error messages) - -L - -L - -=head2 Historical database record classes - -L - History table base class - -L - Historical record of customer payment changes - -L - Historical record of customer credit changes - -L - Historical record of customer tax changes (old-style) - -L - Object method for h_cust_svc objects - -L - Historical record of customer tax changes (old-style) - -L - Historical DNS entry objects - -L - Historical account objects - -L - Historical broadband connection objects - -L - Historical domain objects - -L - Historical externally tracked service objects - -L - Historical mail forwarding alias objects - -L - Historical mailing list objects - -L - Historical phone number objects - -L - Historical PBX objects - -L - Historical web virtual host objects - -=head2 Remote API modules - -L - Self-service API - -L - Self-service XML-RPC API - -=head2 User Interface classes - -L - Web user-interface class - -L - Byte counter user-interface class - -=head2 Command-line utilities - -L - Command line interface to add (freeside) users. - -L - Run daily billing and collection events. - -L - Run monthly billing and invoice collection events. - -L - Recreate database schema cache - -L - Command line interface to delete (freeside) users. - -L - Emails notifications of credit card expirations. - -L - Prints email addresses of all users on STDOUT - -L - Send a freeside page to a list of employees. - -L - Real-time daemon for prepaid packages - -L - Removes stray applications of credit, payment to bills, refunds, etc. - -L - Job queue daemon - -L - Command line utility to manipulate radius groups - -L - Command line tool to re-trigger export jobs for existing services - -L - Command line tool to set the fixed columns for existing services - -L - Command line tool to eliminate duplicate usergroup entries from radius tables - -L - Real-time radacct import daemon - -L - Command line interface to reset and recreate RADIUS SQL tables - -L - Command line time-online tool - -L - Upgrades database schema for new freeside verisons. - -=head1 Notes - -To quote perl(1), "If you're intending to read these straight through for the -first time, the suggested order will tend to reduce the number of forward -references." - -If you've never used OO modules before, -http://www.perl.com/doc/FMTEYEWTK/easy_objects.html might help you out. - -=head1 DESCRIPTION - -Freeside is a billing and administration package for wired and wireless ISPs, -VoIP, hosting, service and content providers and other online businesses. - -The Freeside home page is at . - -The main documentation is at . - -=head1 SUPPORT - -A mailing list for users is available. Send a blank message to - to subscribe. - -A mailing list for developers is available. It is intended to be lower volume -and higher SNR than the users list. Send a blank message to - to subscribe. - -Commercial support is available; see -. - -=head1 AUTHORS - -Primarily Ivan Kohler, with help from many kind folks, including core -contributors Jeff Finucane, Kristian Hoffman, Jason Hall and Peter Bowen. - -See the CREDITS file in the Freeside distribution for a (hopefully) complete -list and the individal files for details. - -=head1 SEE ALSO - -perl(1), main Freeside documentation at - -=head1 BUGS - -Those modules which would be useful separately should be pulled out, -renamed appropriately and uploaded to CPAN. So far: DBIx::DBSchema, Net::SSH -and Net::SCP... - -=cut - diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm deleted file mode 100644 index adb4a0667..000000000 --- a/FS/FS/AccessRight.pm +++ /dev/null @@ -1,401 +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', - - ### - # contact rights - ### - 'Contact and Prospect rights' => [ - 'New prospect', - 'View prospect', - 'Edit prospect', - 'List prospects', - 'Edit contact', #! - #'New contact', - #'View customer contacts', - #'List contacts', - ], - - ### - # basic customer rights - ### - 'Customer rights' => [ - 'New customer', - 'View customer', - #'View Customer | View tickets', - 'Edit customer', - 'Edit customer tags', - 'Edit referring customer', - 'View customer history', - 'Cancel customer', - 'Complimentary customer', #aka users-allow_comp - 'Merge customer', - { 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 - 'Bill customer now', #NEW - 'Bulk send customer notices', #NEW - { rightname=>'View customers of all agents', global=>1 }, - ], - - ### - # 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', - 'Discount customer package', #NEW - 'Custom discount customer package', #NEW - 'Customize customer package', - 'Suspend customer package', - 'Suspend customer package later', - 'Unsuspend customer package', - 'Cancel customer package immediately', - 'Cancel customer package later', - 'Delay suspension events', - 'Add on-the-fly cancel reason', #NEW - 'Add on-the-fly suspend reason', #NEW - 'Edit customer package invoice details', #NEW - 'Edit customer package comments', #NEW - 'Qualify service', #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 - 'Manage domain registration', - - { 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 - 'Delete invoices', #new, but no need to phase in - 'View customer tax exemptions', #yow - 'Add customer tax adjustment', #new, but no need to phase in - 'View customer batched payments', #NEW - 'View customer pending payments', #NEW - 'Edit customer pending payments', #NEW - 'View customer billing events', #NEW - ], - - ### - # customer payment rights - ### - 'Customer payment rights' => [ - { rightname=>'Post payment', desc=>'Make check or cash payments.' }, - 'Post check payment', - 'Post cash payment', - 'Post payment batch', - 'Apply payment', #NEWNEW - { rightname=>'Unapply payment', desc=>'Enable "unapplication" of unclosed payments from specific invoices.' }, #aka. unapplypayments - { rightname=>'Process payment', desc=>'Process credit card or e-check payments' }, - 'Process credit card payment', - 'Process Echeck 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. - { rightname=>'Post refund', desc=>'Enable posting of check and cash refunds.' }, - 'Post check refund', - 'Post cash refund', -# { rightname=>'Process refund', desc=>'Enable processing of generic credit card/ACH refunds (i.e. not associated with a specific prior payment).' }, - { rightname=>'Refund payment', desc=>'Enable refund of existing customer credit card or e-check payments.' }, - 'Refund credit card payment', - 'Refund Echeck payment', - '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 - - - ], - - ### - # note/attachment rights... - ### - 'Customer note and attachment rights' => [ - 'Add customer note', #NEW - 'Edit customer note', #NEW - 'View attachments', #NEW - 'Browse attachments', #NEW - 'Download attachment', #NEW - 'Add attachment', #NEW - 'Edit attachment', #NEW - 'Delete attachment', #NEW - 'View deleted attachments', #NEW - 'Undelete attachment', #NEW - 'Purge attachment', #NEW - ], - - ### - # report/listing rights... - ### - 'Reporting/listing rights' => [ - 'List customers', - 'List zip codes', #NEW - 'List invoices', - 'List packages', - 'List services', - 'List service passwords', - - { rightname=> 'List rating data', desc=>'Usage reports', global=>1 }, - 'Billing event reports', - 'Receivables report', - 'Financial reports', - - #{ rightname => 'List customers of all agents', global=>1 }, - ], - - ### - # 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=>'Redownload resolved batches', global=>1 }, - { rightname=>'Import', global=>1 }, #some of these are ag-virt'ed now? give em their own ACLs - { rightname=>'Export', global=>1 }, - { rightname=> 'Edit rating data', desc=>'Delete CDRs', 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 }, - - 'Edit templates', - { rightname=>'Edit global templates', global=>1 }, - - 'Edit inventory', - { rightname=>'Edit global inventory', global=>1 }, - - { rightname=>'Dialup configuration' }, - { rightname=>'Dialup global configuration', global=>1 }, - - { rightname=>'Broadband configuration' }, - { rightname=>'Broadband global configuration', global=>1 }, - - #{ rightname=>'Edit employees', global=>1, }, - #{ rightname=>'Edit employee groupss', global=>1, }, - - { rightname=>'Configuration', global=>1 }, #most of the rest of the configuraiton is not agent-virtualized - - { rightname=>'Configuration download', }, #description of how it affects - #search/elements/search.html - - ], - -; - -=head1 CLASS METHODS - -=over 4 - -=item rights - -Returns the full list of right names. - -=cut - -sub rights { - #my $class = shift; - map { ref($_) ? $_->{'rightname'} : $_ } map @{ $rights{$_} }, keys %rights; -} - -=item default_superuser_rights - -Most (but not all) right names. - -=cut - -sub default_superuser_rights { - my $class = shift; - my %omit = map { $_=>1 } ( - 'Delete customer', - 'Delete invoices', - 'Delete payment', - 'Delete credit', #? - 'Delete refund', #? - 'Time queue', - 'Redownload resolved batches', - 'Raw SQL', - 'Configuration download', - 'View customers of all agents', - 'View/link unlinked services', - 'Edit usage', - ); - - no warnings 'uninitialized'; - grep { ! $omit{$_} } $class->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, L, L - -=cut - -1; - diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm deleted file mode 100644 index 945478475..000000000 --- a/FS/FS/CGI.pm +++ /dev/null @@ -1,333 +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 - 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 = < - - - $title - - - - - - - -
$title
-
-
-END - $x .= $menubar. "

" 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!$item!; - } - 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 < - - Error processing your request - - - - - -
-

Error processing your request

-
- Your request could not be processed because of the following error: -

$error - - -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 [URL] - -Returns current (or, optionally, passed) URL with LEVEL levels of path removed -from the end (default 0). - -=cut - -sub popurl { - my $up = shift; - - my $url_string; - if ( scalar(@_) ) { - $url_string = shift; - } else { - my $cgi = &FS::UID::cgi; - $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 { - my $url_string; - if ( scalar(@_) ) { - $url_string = shift; - } else { - # better to start with the client-provided URL - my $cgi = &FS::UID::cgi; - $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!!; - } else { - '
'; - } -} - -=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!
!; - } else { - qq!
!; - } -} - -=item ntable - -This is getting silly. - -=cut - -sub ntable { - my $col = shift; - my $cellspacing = shift || 0; - if ( $col ) { - qq!
!; - } else { - '
'; - } - -} - -=back - -=head1 BUGS - -Not OO. - -Not complete. - -=head1 SEE ALSO - -L, L - -=cut - -1; - - diff --git a/FS/FS/ClientAPI.pm b/FS/FS/ClientAPI.pm deleted file mode 100644 index 1677fcc5d..000000000 --- a/FS/FS/ClientAPI.pm +++ /dev/null @@ -1,42 +0,0 @@ -package FS::ClientAPI; - -use strict; -use base 'Exporter'; -use vars qw( @EXPORT_OK %handler $domain $DEBUG ); - -@EXPORT_OK = qw( load_clientapi_modules ); - -$DEBUG = 0; - -%handler = (); - -sub load_clientapi_modules { - - #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 923920d7f..000000000 --- a/FS/FS/ClientAPI/Agent.pm +++ /dev/null @@ -1,214 +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::Search qw(smart_search); -use FS::svc_domain; -use FS::svc_acct; - -sub _cache { - $cache ||= new FS::ClientAPI_SessionCache( { - 'namespace' => 'FS::ClientAPI::Agent', - } ); -} - -sub new_agent { - my $p = shift; - - my $conf = new FS::Conf; - return { error=>'Disabled' } unless $conf->exists('selfservice-agent_signup'); - - #add a customer record and set agent_custnum? - - my $agent = new FS::agent { - 'typenum' => $conf->config('selfservice-agent_signup-agent_type'), - 'agent' => $p->{'agent'}, - 'username' => $p->{'username'}, - '_password' => $p->{'password'}, - # - }; - - my $error = $agent->insert; - - return { 'error' => $error } if $error; - - agent_login({ 'username' => $p->{'username'}, - 'password' => $p->{'password'}, - }); -} - -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 - ], - } - -} - -sub check_username { - my $p = shift; - my($session, $agentnum, $svc_acct) = _session_agentnum_svc_acct($p); - return { 'error' => $session } unless ref($session); - - { 'error' => '', - #'username' => $username, - #'domain' => $domain, - 'available' => $svc_acct ? 0 : 1, - }; - -} - -sub _session_agentnum_svc_acct { - my $p = shift; - - my $session = _cache->get($p->{'session_id'}) - or return "Can't resume session"; #better error message - - my $username = $p->{'username'}; - - #XXX some way to default this per agent (by default product's service def?) - my $domain = $p->{'domain'}; - - my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } ) - or return { 'error' => 'Unknown domain' }; - - my $svc_acct = qsearchs('svc_acct', { 'username' => $username, - 'domsvc' => $svc_domain->svcnum, } ); - - ( $session, $session->{'agentnum'}, $svc_acct ); - -} - -sub _session_agentnum_cust_pkg { - my $p = shift; - my($session, $agentnum, $svc_acct) = _session_agentnum_svc_acct($p); - return $session unless ref($session); - return 'Account not found' unless $svc_acct; - my $cust_svc = $svc_acct->cust_svc; - return 'Unlinked account' unless $cust_svc->pkgnum; - my $cust_pkg = $cust_svc->cust_pkg; - return 'Not your account' unless $cust_pkg->cust_main->agentnum == $agentnum; - ($session, $agentnum, $cust_pkg); -} - -sub suspend_username { - my $p = shift; - my($session, $agentnum, $cust_pkg) = _session_agentnum_cust_pkg($p); - return { 'error' => $session } unless ref($session); - - return { 'error' => $cust_pkg->suspend }; -} - -sub unsuspend_username { - my $p = shift; - my($session, $agentnum, $cust_pkg) = _session_agentnum_cust_pkg($p); - return { 'error' => $session } unless ref($session); - - return { 'error' => $cust_pkg->unsuspend }; -} - -1; diff --git a/FS/FS/ClientAPI/Bulk.pm b/FS/FS/ClientAPI/Bulk.pm deleted file mode 100644 index ec617df76..000000000 --- a/FS/FS/ClientAPI/Bulk.pm +++ /dev/null @@ -1,384 +0,0 @@ -package FS::ClientAPI::Bulk; - -use strict; - -use vars qw( $DEBUG $cache ); -use Date::Parse; -use FS::Record qw( qsearchs ); -use FS::Conf; -use FS::ClientAPI_SessionCache; -use FS::cust_main; -use FS::cust_pkg; -use FS::cust_svc; -use FS::svc_acct; -use FS::svc_external; -use FS::cust_recon; -use Data::Dumper; - -$DEBUG = 1; - -sub _cache { - $cache ||= new FS::ClientAPI_SessionCache ( { - 'namespace' => 'FS::ClientAPI::Agent', #yes, share session_ids - } ); -} - -sub _izoom_ftp_row_fixup { - my $hash = shift; - - my @addr_fields = qw( address1 address2 city state zip ); - my @fields = ( qw( agent_custid username _password first last ), - @addr_fields, - map { "ship_$_" } @addr_fields ); - - $hash->{$_} =~ s/[&\/\*'"]/_/g foreach @fields; - - #$hash->{action} = '' if $hash->{action} eq 'R'; #unsupported for ftp - - $hash->{refnum} = 1; #ahem - $hash->{country} = 'US'; - $hash->{ship_country} = 'US'; - $hash->{payby} = 'LECB'; - $hash->{payinfo} = $hash->{daytime}; - $hash->{ship_fax} = '' if ( !$hash->{sms} || $hash->{sms} eq 'F' ); - - my $has_ship = - grep { $hash->{"ship_$_"} && - (! $hash->{$_} || $hash->{"ship_$_"} ne $hash->{$_} ) - } - ( @addr_fields, 'fax' ); - - if ( $has_ship ) { - foreach ( @addr_fields, qw( first last ) ) { - $hash->{"ship_$_"} = $hash->{$_} unless $hash->{"ship_$_"}; - } - } - - delete $hash->{sms}; - - ''; - -}; - -sub _izoom_ftp_result { - my ($hash, $error) = @_; - my $cust_main = - qsearchs( 'cust_main', { 'agent_custid' => $hash->{agent_custid}, - 'agentnum' => $hash->{agentnum} - } - ); - - my $custnum = $cust_main ? $cust_main->custnum : ''; - my @response = ( $hash->{action}, $hash->{agent_custid}, $custnum ); - - if ( $error ) { - push @response, ( 'ERROR', $error ); - } else { - push @response, ( 'OK', 'OK' ); - } - - join( ',', @response ); - -} - -sub _izoom_ftp_badaction { - "Invalid action: $_[0] record: @_ "; -} - -sub _izoom_soap_row_fixup { _izoom_ftp_row_fixup(@_) }; - -sub _izoom_soap_result { - my ($hash, $error) = @_; - - if ( $hash->{action} eq 'R' ) { - if ( $error ) { - return "Please check errors:\n $error"; # odd extra space - } else { - return join(' ', "Everything ok.", $hash->{pkg}, $hash->{adjourn} ); - } - } - - my $pkg = $hash->{pkg} || $hash->{saved_pkg} || ''; - if ( $error ) { - return join(' ', $hash->{agent_custid}, $error ); - } else { - return join(' ', $hash->{agent_custid}, $pkg, $hash->{adjourn} ); - } - -} - -sub _izoom_soap_badaction { - "Unknown action '$_[13]' "; -} - -my %format = ( - 'izoom-ftp' => { - 'fields' => [ qw ( action agent_custid username _password - daytime ship_fax sms first last - address1 address2 city state zip - pkg adjourn ship_address1 ship_address2 - ship_city ship_state ship_zip ) ], - 'fixup' => sub { _izoom_ftp_row_fixup(@_) }, - 'result' => sub { _izoom_ftp_result(@_) }, - 'action' => sub { _izoom_ftp_badaction(@_) }, - }, - 'izoom-soap' => { - 'fields' => [ qw ( agent_custid username _password - daytime first last address1 address2 - city state zip pkg action adjourn - ship_fax sms ship_address1 ship_address2 - ship_city ship_state ship_zip ) ], - 'fixup' => sub { _izoom_soap_row_fixup(@_) }, - 'result' => sub { _izoom_soap_result(@_) }, - 'action' => sub { _izoom_soap_badaction(@_) }, - }, -); - -sub processrow { - my $p = shift; - - my $session = _cache->get($p->{'session_id'}) - or return { 'error' => "Can't resume session" }; #better error message - - my $conf = new FS::Conf; - my $format = $conf->config('selfservice-bulk_format', $session->{agentnum}) - || 'izoom-soap'; - my ( @row ) = @{ $p->{row} }; - - warn "processrow called with '". join("' '", @row). "'\n" if $DEBUG; - - return { 'error' => "unknown format: $format" } - unless exists $format{$format}; - - return { 'error' => "Invalid record record length: ". scalar(@row). - "record: @row " #sic - } - unless scalar(@row) == scalar(@{$format{$format}{fields}}); - - my %hash = ( 'agentnum' => $session->{agentnum} ); - my $error; - - foreach my $field ( @{ $format{ $format }{ fields } } ) { - $hash{$field} = shift @row; - } - - $error ||= &{ $format{ $format }{ fixup } }( \%hash ); - - # put in the fixup routine? - if ( 'R' eq $hash{action} ) { - warn "processing reconciliation\n" if $DEBUG; - $error ||= process_recon($hash{agentnum}, $hash{agent_custid}); - } elsif ( 'P' eq $hash{action} ) { - # do nothing - } elsif( 'D' eq $hash{action} ) { - $hash{promo_pkg} = 'disk-1-'. $session->{agent}; - } elsif ( 'S' eq $hash{action} ) { - $hash{promo_pkg} = 'disk-2-'. $session->{agent}; - $hash{saved_pkg} = $hash{pkg}; - $hash{pkg} = ''; - } else { - $error ||= &{ $format{ $format }{ action } }( @row ); - } - - warn "processing provision\n" if ($DEBUG && !$error && $hash{action} ne 'R'); - $error ||= provision( %hash ) unless $hash{action} eq 'R'; - - my $result = &{ $format{ $format }{ result } }( \%hash, $error ); - - warn "processrow returning '". join("' '", $result, $error). "'\n" - if $DEBUG; - - return { 'error' => $error, 'message' => $result }; - -} - -sub provision { - my %args = ( @_ ); - - delete $args{action}; - - my $cust_main = - qsearchs( 'cust_main', - { map { $_ => $args{$_} } qw ( agent_custid agentnum ) }, - ); - - unless ( $cust_main ) { - $cust_main = new FS::cust_main { %args }; - my $error = $cust_main->insert; - return $error if $error; - } - - my @pkgs = grep { $_->part_pkg->freq } $cust_main->ncancelled_pkgs; - if ( scalar(@pkgs) > 1 ) { - return "Invalid account, should not be more then one active package ". #sic - "but found: ". scalar(@pkgs). " packages."; - } - - my $part_pkg = qsearchs( 'part_pkg', { 'pkg' => $args{pkg} } ) - or return "Unknown pkgpart: $args{pkg}" - if $args{pkg}; - - - my $create_package = $args{pkg}; - if ( scalar(@pkgs) && $create_package ) { - my $pkg = pop(@pkgs); - - if ( $part_pkg->pkgpart != $pkg->pkgpart ) { - my @cust_bill_pkg = $pkg->cust_bill_pkg(); - if ( 1 == scalar(@cust_bill_pkg) ) { - my $cbp= pop(@cust_bill_pkg); - my $cust_bill = $cbp->cust_bill; - $cust_bill->delete(); #really? wouldn't a credit be better? - } - $pkg->cancel(); - } else { - $create_package = ''; - $pkg->setfield('adjourn', str2time($args{adjourn})); - my $error = $pkg->replace(); - return $error if $error; - } - } - - if ( $create_package ) { - my $cust_pkg = new FS::cust_pkg ( { - 'pkgpart' => $part_pkg->pkgpart, - 'adjourn' => str2time( $args{adjourn} ), - } ); - - my $svcpart = $part_pkg->svcpart('svc_acct'); - - my $svc_acct = new FS::svc_acct ( { - 'svcpart' => $svcpart, - 'username' => $args{username}, - '_password' => $args{_password}, - } ); - - my $error = $cust_main->order_pkg( cust_pkg => $cust_pkg, - svcs => [ $svc_acct ], - ); - return $error if $error; - } - - if ( $args{promo_pkg} ) { - my $part_pkg = - qsearchs( 'part_pkg', { 'promo_code' => $args{promo_pkg} } ) - or return "unknown pkgpart: $args{promo_pkg}"; - - my $svcpart = $part_pkg->svcpart('svc_external') - or return "unknown svcpart: svc_external"; - - my $cust_pkg = new FS::cust_pkg ( { - 'svcpart' => $svcpart, - 'pkgpart' => $part_pkg->pkgpart, - } ); - - my $svc_ext = new FS::svc_external ( { 'svcpart' => $svcpart } ); - - my $ticket_subject = 'Send setup disk to customer '. $cust_main->custnum; - my $error = $cust_main->order_pkg ( cust_pkg => $cust_pkg, - svcs => [ $svc_ext ], - noexport => 1, - ticket_subject => $ticket_subject, - ticket_queue => "disk-$args{agentnum}", - ); - return $error if $error; - } - - my $error = $cust_main->bill(); - return $error if $error; -} - -sub process_recon { - my ( $agentnum, $id ) = @_; - my @recs = split /;/, $id; - my $err = ''; - foreach my $rec ( @recs ) { - my @record = split /,/, $rec; - my $result = process_recon_record(@record, $agentnum); - $err .= "$result\n" if $result; - } - return $err; -} - -sub process_recon_record { - my ( $agent_custid, $username, $_password, $daytime, $first, $last, $address1, $address2, $city, $state, $zip, $pkg, $adjourn, $agentnum) = @_; - - warn "process_recon_record called with '". join("','", @_). "'\n" if $DEBUG; - - my ($cust_pkg, $package); - - my $cust_main = - qsearchs( 'cust_main', - { 'agent_custid' => $agent_custid, 'agentnum' => $agentnum }, - ); - - my $comments = ''; - if ( $cust_main ) { - my @cust_pkg = grep { $_->part_pkg->freq } $cust_main->ncancelled_pkgs; - if ( scalar(@cust_pkg) == 1) { - $cust_pkg = pop(@cust_pkg); - $package = $cust_pkg->part_pkg->pkg; - $comments = "$agent_custid wrong package, expected: $pkg found: $package" - if ( $pkg ne $package ); - } else { - $comments = "invalid account, should be one active package but found: ". - scalar(@cust_pkg). " packages."; - } - } else { - $comments = - "Customer not found agent_custid=$agent_custid, agentnum=$agentnum"; - } - - my $cust_recon = new FS::cust_recon( { - 'recondate' => time, - 'agentnum' => $agentnum, - 'first' => $first, - 'last' => $last, - 'address1' => $address1, - 'address2' => $address2, - 'city' => $city, - 'state' => $state, - 'zip' => $zip, - 'custnum' => $cust_main ? $cust_main->custnum : '', #really? - 'status' => $cust_main ? $cust_main->status : '', - 'pkg' => $package, - 'adjourn' => $cust_pkg ? $cust_pkg->adjourn : '', - 'agent_custid' => $agent_custid, # redundant? - 'agent_pkg' => $pkg, - 'agent_adjourn' => str2time($adjourn), - 'comments' => $comments, - } ); - - warn Dumper($cust_recon) if $DEBUG; - my $error = $cust_recon->insert; - return $error if $error; - - warn "process_recon_record returning $comments\n" if $DEBUG; - - $comments; - -} - -sub check_username { - my $p = shift; - - my $session = _cache->get($p->{'session_id'}) - or return { 'error' => "Can't resume session" }; #better error message - - my $svc_domain = qsearchs( 'svc_domain', { 'domain' => $p->{domain} } ) - or return { 'error' => 'Unknown domain '. $p->{domain} }; - - my $svc_acct = qsearchs( 'svc_acct', { 'username' => $p->{user}, - 'domsvc' => $svc_domain->svcnum, - }, - ); - - return { 'error' => $p->{user}. '@'. $p->{domain}. " alerady in use" } # sic - if $svc_acct; - - return { 'error' => '', - 'message' => $p->{user}. '@'. $p->{domain}. " is free" - }; -} - -1; diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm deleted file mode 100644 index 20b4e5bdb..000000000 --- a/FS/FS/ClientAPI/MasonComponent.pm +++ /dev/null @@ -1,131 +0,0 @@ -package FS::ClientAPI::MasonComponent; - -use strict; -use vars qw( $cache $DEBUG $me ); -use subs qw( _cache ); -use FS::Mason qw( mason_interps ); -use FS::Conf; -use FS::ClientAPI_SessionCache; -use FS::Record qw( qsearch qsearchs ); -use FS::cust_main; -use FS::part_pkg; - -$DEBUG = 0; -$me = '[FS::ClientAPI::MasonComponent]'; - -my %allowed_comps = map { $_=>1 } qw( - /elements/select-did.html - /misc/areacodes.cgi - /misc/exchanges.cgi - /misc/phonenums.cgi - /misc/states.cgi - /misc/counties.cgi - /misc/svc_acct-domains.cgi - /misc/part_svc-columns.cgi -); - -my %session_comps = map { $_=>1 } qw( - /elements/location.html - /edit/cust_main/first_pkg/select-part_pkg.html -); - -my %session_callbacks = ( - - '/elements/location.html' => sub { - my( $custnum, $argsref ) = @_; - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or return "unknown custnum $custnum"; - my %args = @$argsref; - $args{object} = $cust_main; - @$argsref = ( %args ); - return ''; #no error - }, - - '/edit/cust_main/first_pkg/select-part_pkg.html' => sub { - my( $custnum, $argsref ) = @_; - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or return "unknown custnum $custnum"; - - my $pkgpart = $cust_main->agent->pkgpart_hashref; - - #false laziness w/ edit/cust_main/first_pkg.html - my @first_svc = ( 'svc_acct', 'svc_phone' ); - - my @part_pkg = - grep { $_->svcpart(\@first_svc) - && ( $pkgpart->{ $_->pkgpart } - || ( $_->agentnum && $_->agentnum == $cust_main->agentnum ) - ) - } - qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case? - - my $conf = new FS::Conf; - if ( $conf->exists('pkg-addon_classnum') ) { - - my %classnum = map { ( $_->addon_classnum => 1 ) } - grep { $_->freq !~ /^0/ } - map { $_->part_pkg } - $cust_main->ncancelled_pkgs; - - unless ( $classnum{''} || ! keys %classnum ) { - @part_pkg = grep $classnum{ $_->classnum }, @part_pkg; - } - } - - my %args = @$argsref; - $args{part_pkg} = \@part_pkg; - @$argsref = ( %args ); - return ''; #no error - - }, - -); - -my $outbuf; -my( $fs_interp, $rt_interp ) = mason_interps('standalone', 'outbuf'=>\$outbuf); - -sub mason_comp { - my $packet = shift; - - warn "$me mason_comp called on $packet\n" if $DEBUG; - - my $comp = $packet->{'comp'}; - unless ( $allowed_comps{$comp} || $session_comps{$comp} ) { - return { 'error' => 'Illegal component' }; - } - - my @args = $packet->{'args'} ? @{ $packet->{'args'} } : (); - - if ( $session_comps{$comp} ) { - - my $session = _cache->get($packet->{'session_id'}) - or return ( 'error' => "Can't resume session" ); #better error message - my $custnum = $session->{'custnum'}; - - my $error = &{ $session_callbacks{$comp} }( $custnum, \@args ); - return { 'error' => $error } if $error; - - } - - my $conf = new FS::Conf; - $FS::Mason::Request::FSURL = $conf->config('selfservice_server-base_url'); - $FS::Mason::Request::FSURL .= '/' unless $FS::Mason::Request::FSURL =~ /\/$/; - $FS::Mason::Request::QUERY_STRING = $packet->{'query_string'} || ''; - - $outbuf = ''; - $fs_interp->exec($comp, @args); #only FS for now alas... - - #errors? (turn off in-line error reporting?) - - return { 'output' => $outbuf }; - -} - -#hmm -sub _cache { - $cache ||= new FS::ClientAPI_SessionCache( { - 'namespace' => 'FS::ClientAPI::MyAccount', - } ); -} - -1; diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm deleted file mode 100644 index d619e8493..000000000 --- a/FS/FS/ClientAPI/MyAccount.pm +++ /dev/null @@ -1,2039 +0,0 @@ -package FS::ClientAPI::MyAccount; - -use 5.008; #require 5.8+ for Time::Local 1.05+ -use strict; -use vars qw( $cache $DEBUG $me ); -use subs qw( _cache _provision ); -use Data::Dumper; -use Digest::MD5 qw(md5_hex); -use Date::Format; -use Business::CreditCard; -use Time::Duration; -use Time::Local qw(timelocal_nocheck); -use FS::UI::Web::small_custview qw(small_custview); #less doh -use FS::UI::Web; -use FS::UI::bytecount qw( display_bytecount ); -use FS::Conf; -#use FS::UID qw(dbh); -use FS::Record qw(qsearch qsearchs dbh); -use FS::Msgcat qw(gettext); -use FS::Misc qw(card_types); -use FS::Misc::DateTime qw(parse_datetime); -use FS::ClientAPI_SessionCache; -use FS::svc_acct; -use FS::svc_domain; -use FS::svc_phone; -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; -use FS::TicketSystem; -use Text::CSV_XS; -use IO::Scalar; -use Spreadsheet::WriteExcel; - -$DEBUG = 0; -$me = '[FS::ClientAPI::MyAccount]'; - -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 -); - -sub _cache { - $cache ||= new FS::ClientAPI_SessionCache( { - 'namespace' => 'FS::ClientAPI::MyAccount', - } ); -} - -sub skin_info { - my $p = shift; - - my($context, $session, $custnum) = _custoragent_session_custnum($p); - #return { 'error' => $session } if $context eq 'error'; - - my $agentnum = ''; - if ( $context eq 'customer' ) { - - my $sth = dbh->prepare('SELECT agentnum FROM cust_main WHERE custnum = ?') - or die dbh->errstr; - - $sth->execute($custnum) or die $sth->errstr; - - $agentnum = $sth->fetchrow_arrayref->[0] - or die "no agentnum for custnum $custnum"; - - #} elsif ( $context eq 'agent' ) { - } elsif ( $p->{'agentnum'} =~ /^(\d+)$/ ) { - $agentnum = $1; - } - - my $conf = new FS::Conf; - - #false laziness w/Signup.pm - - my $skin_info_cache_agent = _cache->get("skin_info_cache_agent$agentnum"); - - if ( $skin_info_cache_agent ) { - - warn "$me loading cached skin info for agentnum $agentnum\n" - if $DEBUG > 1; - - } else { - - warn "$me populating skin info cache for agentnum $agentnum\n" - if $DEBUG > 1; - - $skin_info_cache_agent = { - 'agentnum' => $agentnum, - ( map { $_ => scalar( $conf->config($_, $agentnum) ) } - qw( company_name ) ), - ( map { $_ => scalar( $conf->config("selfservice-$_", $agentnum ) ) } - qw( body_bgcolor box_bgcolor - text_color link_color vlink_color hlink_color alink_color - font title_color title_align title_size menu_bgcolor menu_fontsize - ) - ), - ( map { $_ => $conf->exists("selfservice-$_", $agentnum ) } - qw( menu_skipblanks menu_skipheadings menu_nounderline ) - ), - ( map { $_ => scalar($conf->config_binary("selfservice-$_", $agentnum)) } - qw( title_left_image title_right_image - menu_top_image menu_body_image menu_bottom_image - ) - ), - 'logo' => scalar($conf->config_binary('logo.png', $agentnum )), - ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) } - qw( head body_header body_footer company_address ) ), - }; - - _cache->set("skin_info_cache_agent$agentnum", $skin_info_cache_agent); - - } - - #{ %$skin_info_cache_agent }; - $skin_info_cache_agent; - -} - -sub login_info { - my $p = shift; - - my $conf = new FS::Conf; - - my %info = ( - %{ skin_info($p) }, - 'phone_login' => $conf->exists('selfservice_server-phone_login'), - 'single_domain'=> scalar($conf->config('selfservice_server-single_domain')), - ); - - return \%info; - -} - -#false laziness w/FS::ClientAPI::passwd::passwd -sub login { - my $p = shift; - - my $conf = new FS::Conf; - - my $svc_x = ''; - if ( $p->{'domain'} eq 'svc_phone' - && $conf->exists('selfservice_server-phone_login') ) { - - my $svc_phone = qsearchs( 'svc_phone', { 'phonenum' => $p->{'username'} } ); - return { error => 'Number not found.' } unless $svc_phone; - - #XXX? - #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 PIN.' } - unless $svc_phone->check_pin($p->{'password'}); - - $svc_x = $svc_phone; - - } else { - - 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; - - if($conf->exists('selfservice_server-login_svcpart')) { - my @svcpart = $conf->config('selfservice_server-login_svcpart'); - my $svcpart = $svc_acct->cust_svc->svcpart; - return { error => 'Invalid user.' } - unless grep($_ eq $svcpart, @svcpart); - } - - return { error => 'Incorrect password.' } - unless $svc_acct->check_password($p->{'password'}); - - $svc_x = $svc_acct; - - } - - my $session = { - 'svcnum' => $svc_x->svcnum, - }; - - my $cust_svc = $svc_x->cust_svc; - my $cust_pkg = $cust_svc->cust_pkg; - if ( $cust_pkg ) { - my $cust_main = $cust_pkg->cust_main; - $session->{'custnum'} = $cust_main->custnum; - if ( $conf->exists('pkg-balances') ) { - my @cust_pkg = grep { $_->part_pkg->freq !~ /^(0|$)/ } - $cust_main->ncancelled_pkgs; - $session->{'pkgnum'} = $cust_pkg->pkgnum - if scalar(@cust_pkg) > 1; - } - } - - #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' ); - my $part_pkg = $cust_pkg->part_pkg; - return { error => 'Only primary user may log in.' } - if $conf->exists('selfservice_server-primary_only') - && $cust_svc->svcpart != $part_pkg->svcpart([qw( svc_acct svc_phone )]); - - 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 { %{ skin_info($p) }, 'error' => '' }; - } else { - return { %{ skin_info($p) }, 'error' => "Can't resume session" }; #better error message - } -} - -sub payment_gateway { - # internal use only - # takes a cust_main and a cust_payby entry, returns the payment_gateway - my $conf = new FS::Conf; - my $cust_main = shift; - my $cust_payby = shift; - my $gatewaynum = $conf->config('selfservice-payment_gateway'); - if ( $gatewaynum ) { - my $pg = qsearchs('payment_gateway', { gatewaynum => $gatewaynum }); - die "configured gatewaynum $gatewaynum not found!" if !$pg; - return $pg; - } - else { - return '' if ! FS::payby->realtime($cust_payby); - my $pg = $cust_main->agent->payment_gateway( - 'method' => FS::payby->payby2bop($cust_payby), - 'nofatal' => 1 - ); - return $pg; - } -} - -sub access_info { - my $p = shift; - - my $conf = new FS::Conf; - - my $info = skin_info($p); - - use vars qw( $cust_paybys ); #cache for performance - unless ( $cust_paybys ) { - - my %cust_paybys = map { $_ => 1 } - map { FS::payby->payby2payment($_) } - $conf->config('signup_server-payby'); - - $cust_paybys = [ keys %cust_paybys ]; - - } - $info->{'cust_paybys'} = $cust_paybys; - - my($context, $session, $custnum) = _custoragent_session_custnum($p); - return { 'error' => $session } if $context eq 'error'; - - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or return { 'error' => "unknown custnum $custnum" }; - - $info->{'hide_payment_fields'} = [ - map { - my $pg = payment_gateway($cust_main, $_); - $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment'; - } @{ $info->{cust_paybys} } - ]; - - $info->{'self_suspend_reason'} = - $conf->config('selfservice-self_suspend_reason', $cust_main->agentnum); - - return { %$info, - 'custnum' => $custnum, - 'access_pkgnum' => $session->{'pkgnum'}, - 'access_svcnum' => $session->{'svcnum'}, - }; -} - -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" }; - - if ( $session->{'pkgnum'} ) { - $return{balance} = $cust_main->balance_pkgnum( $session->{'pkgnum'} ); - } else { - $return{balance} = $cust_main->balance; - } - - $return{tickets} = [ ($cust_main->tickets) ]; - - unless ( $session->{'pkgnum'} ) { - 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, - scalar($conf->config('countrydefault')), - ( $session->{'pkgnum'} ? 1 : 0 ), #nobalance - ); - - $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; - } - - if ( $conf->config('prepayment_discounts-credit_type') ) { - #need to eval? - $return{discount_terms_hash} = { $cust_main->discount_terms_hash }; - } - - } 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; - - $new->payinfo( ($payinfo eq $cust_main->paymask) - ? $cust_main->payinfo - : $payinfo - ); - - $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' ); - - } elsif ( $payby =~ /^(BILL)$/ ) { - #no-op - } 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 - ## - - my $conf = new FS::Conf; - use vars qw($payment_info); #cache for performance - unless ( $payment_info ) { - - my %states = map { $_->state => 1 } - qsearch('cust_main_county', { - 'country' => $conf->config('countrydefault') || 'US' - } ); - - my %cust_paybys = map { $_ => 1 } - map { FS::payby->payby2payment($_) } - $conf->config('signup_server-payby'); - - my @cust_paybys = keys %cust_paybys; - - $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') ], - 'cust_paybys' => \@cust_paybys, - - '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'), - - 'save_unchecked' => $conf->exists('selfservice-save_unchecked'), - }; - - } - - ## - #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{'hide_payment_fields'} = [ - map { - my $pg = payment_gateway($cust_main, $_); - $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment'; - } @{ $return{cust_paybys} } - ]; - - $return{balance} = $cust_main->balance; #XXX pkg-balances? - - $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; - - } - - if ( $conf->config('prepayment_discounts-credit_type') ) { - #need to eval? - $return{discount_terms_hash} = { $cust_main->discount_terms_hash }; - } - - #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->{'amount'} =~ /^\s*(\d+(\.\d{2})?)\s*$/ - or return { 'error' => gettext('illegal_amount') }; - my $amount = $1; - return { error => 'Amount must be greater than 0' } unless $amount > 0; - - $p->{'discount_term'} =~ /^\s*(\d*)\s*$/ - or return { 'error' => gettext('illegal_discount_term'). ': '. $p->{'discount_term'} }; - my $discount_term = $1; - - $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'} ||= 'CARD'; - $p->{'payby'} =~ /^([A-Z]{4})$/ - or return { 'error' => "illegal_payby " . $p->{'payby'} }; - my $payby = $1; - - #false laziness w/process/payment.cgi - 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'}; - - #more intelligent mathing will be needed here if you change - #card_masking_method and don't remove existing paymasks - $payinfo = $cust_main->payinfo - if $cust_main->paymask eq $payinfo; - - $payinfo =~ s/\D//g; - $payinfo =~ /^(\d{13,16})$/ - or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo - $payinfo = $1; - - validate($payinfo) - or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo - return { 'error' => gettext('unknown_card_type') } - if $payinfo !~ /^99\d{14}$/ && 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 payip - address1 address2 city state zip country ) ], - 'CHEK' => [ qw( ss paytype paystate stateid stateid_state payip ) ], - ); - - my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount, - 'quiet' => 1, - 'payinfo' => $payinfo, - 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01', - 'payname' => $payname, - 'paybatch' => $paybatch, #this doesn't actually do anything - 'paycvv' => $paycvv, - 'pkgnum' => $session->{'pkgnum'}, - 'discount_term' => $discount_term, - 'selfservice' => 1, - 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 country ); - $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( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' ); - } - $new->set( 'payinfo' => $cust_main->card_token || $payinfo ); - $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' ); - my $error = $new->replace($cust_main); - if ( $error ) { - #no, this causes customers to process their payments again - #return { 'error' => $error }; - #XXX just warn verosely for now so i can figure out how these happen in - # the first place, eventually should redirect them to the "change - #address" page but indicate the payment did process?? - delete($p->{'payinfo'}); #don't want to log this! - warn "WARNING: error changing customer info when processing payment (not returning to customer as a processing error): $error\n". - "NEW: ". Dumper($new)."\n". - "OLD: ". Dumper($cust_main)."\n". - "PACKET: ". Dumper($p)."\n"; - #} else { - #not needed... - #$cust_main = $new; - } - } - - return { 'error' => '' }; - -} - -sub realtime_collect { - 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 $amount; - if ( $p->{'amount'} ) { - $amount = $p->{'amount'}; - } - elsif ( $session->{'pkgnum'} ) { - $amount = $cust_main->balance_pkgnum( $session->{'pkgnum'} ); - } - else { - $amount = $cust_main->balance; - } - - my $error = $cust_main->realtime_collect( - 'method' => $p->{'method'}, - 'amount' => $amount, - 'pkgnum' => $session->{'pkgnum'}, - 'session_id' => $p->{'session_id'}, - 'apply' => 1, - 'selfservice'=> 1, - ); - return { 'error' => $error } unless ref( $error ); - - return { 'error' => '', amount => $amount, %$error }; -} - -sub process_payment_order_pkg { - my $p = shift; - - my $hr = process_payment($p); - return $hr if $hr->{'error'}; - - order_pkg($p); -} - -sub process_payment_order_renew { - my $p = shift; - - my $hr = process_payment($p); - return $hr if $hr->{'error'}; - - order_renew($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( { unsquelch_cdr => 1 } ), - }; - -} - -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 $agentnum = ''; - if ( $p->{'invnum'} ) { - my $cust_bill = qsearchs('cust_bill', { 'invnum' => $p->{'invnum'} } ) - or return { 'error' => 'unknown invnum' }; - $agentnum = $cust_bill->cust_main->agentnum; - } - - my $templatename = $p->{'template'} || $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, $agentnum), - '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, - 'date' => time2str("%b %o, %Y", $_->_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" }; - - my $conf = new FS::Conf; - -# the duplication below is necessary: -# 1. to maintain the current buggy behaviour wrt the cust_pkg and part_pkg -# hashes overwriting each other (setup and no_auto fields). Fixing that is a -# non-backwards-compatible change breaking the software of anyone using the API -# instead of the stock selfservice -# 2. to return cancelled packages as well - for wholesale and non-wholesale - if( $conf->exists('selfservice_server-view-wholesale') ) { - return { 'svcnum' => $session->{'svcnum'}, - 'custnum' => $custnum, - 'cust_pkg' => [ map { - { $_->hash, - part_pkg => [ map $_->hashref, $_->part_pkg ], - 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->cust_pkg - ], - 'small_custview' => - small_custview( $cust_main, $conf->config('countrydefault') ), - 'wholesale_view' => 1, - 'login_svcpart' => [ $conf->config('selfservice_server-login_svcpart') ], - 'date_format' => $conf->config('date_format') || '%m/%d/%Y', - 'lnp' => $conf->exists('svc_phone-lnp'), - }; - } - - { '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 ) { - next if $session->{'pkgnum'} && $cust_pkg->pkgnum != $session->{'pkgnum'}; - push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context - } - if ( $p->{'svcdb'} ) { - my $svcdb = ref($p->{'svcdb'}) eq 'HASH' - ? $p->{'svcdb'} - : ref($p->{'svcdb'}) eq 'ARRAY' - ? { map { $_=>1 } @{ $p->{'svcdb'} } } - : { $p->{'svcdb'} => 1 }; - @cust_svc = grep $svcdb->{ $_->part_svc->svcdb }, @cust_svc - } - - #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username } - # @svc_x; - - { - 'svcnum' => $session->{'svcnum'}, - 'custnum' => $custnum, - 'svcs' => [ - map { - my $svc_x = $_->svc_x; - my($label, $value) = $_->label; - my $svcdb = $_->part_svc->svcdb; - my $part_pkg = $_->cust_pkg->part_pkg; - - my %hash = ( - 'svcnum' => $_->svcnum, - 'svcdb' => $svcdb, - 'label' => $label, - 'value' => $value, - ); - - if ( $svcdb eq 'svc_acct' ) { - %hash = ( - %hash, - 'username' => $svc_x->username, - 'email' => $svc_x->email, - 'seconds' => $svc_x->seconds, - 'upbytes' => display_bytecount($svc_x->upbytes), - 'downbytes' => display_bytecount($svc_x->downbytes), - 'totalbytes' => display_bytecount($svc_x->totalbytes), - - 'recharge_amount' => $part_pkg->option('recharge_amount',1), - 'recharge_seconds' => $part_pkg->option('recharge_seconds',1), - 'recharge_upbytes' => - display_bytecount($part_pkg->option('recharge_upbytes',1)), - 'recharge_downbytes' => - display_bytecount($part_pkg->option('recharge_downbytes',1)), - 'recharge_totalbytes' => - display_bytecount($part_pkg->option('recharge_totalbytes',1)), - # more... - ); - - } elsif ( $svcdb eq 'svc_phone' ) { - %hash = ( - %hash, - ); - } - - \%hash; - } - @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 _list_cdr_usage { - my($svc_phone, $begin, $end) = @_; - map [ $_->downstream_csv('format' => 'default') ], #XXX config for format - $svc_phone->get_cdrs( 'begin'=>$begin, 'end'=>$end, ); -} - -sub list_cdr_usage { - my $p = shift; - _usage_details( \&_list_cdr_usage, $p, - 'svcdb' => 'svc_phone', - ); -} - -sub _usage_details { - my($callback, $p, %opt) = @_; - - 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 $svcdb = $opt{'svcdb'} || 'svc_acct'; - - my $svc_x = qsearchs( $svcdb, $search ); - return { 'error' => 'No service selected in list_svc_usage' } - unless $svc_x; - - my $header = $svcdb eq 'svc_phone' - ? [ split(',', FS::cdr::invoice_header('default') ) ] #XXX - : []; - - my $cust_pkg = $svc_x->cust_svc->cust_pkg; - my $freq = $cust_pkg->part_pkg->freq; - my $start = $cust_pkg->setup; - #my $end = $cust_pkg->bill; # or time? - my $end = time; - - unless ( $p->{beginning} ) { - $p->{beginning} = $cust_pkg->last_bill; - $p->{ending} = $end; - } - - my (@usage) = &$callback($svc_x, $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, - 'header' => $header, - '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_phone' => [ qw( phonenum pin sip_password phone_name ) ], - 'svc_external' => [ qw( id title ) ], - 'svc_pbx' => [ qw( id name ) ], - ); - - 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; - } - - my $svcnum = $svc[0] ? $svc[0]->svcnum : ''; - - return { error=>'', pkgnum=>$cust_pkg->pkgnum, svcnum=>$svcnum }; - -} - -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->realtime_collect('selfservice' => 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 renew_info { - my $p = shift; - - my($context, $session, $custnum) = _custoragent_session_custnum($p); - return { 'error' => $session } if $context eq 'error'; - - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or return { 'error' => "unknown custnum $custnum" }; - - my @cust_pkg = sort { $a->bill <=> $b->bill } - grep { $_->part_pkg->freq ne '0' } - $cust_main->ncancelled_pkgs; - - #return { 'error' => 'No active packages to renew.' } unless @cust_pkg; - - my $total = $cust_main->balance; - - my @array = map { - my $bill = $_->bill; - $total += $_->part_pkg->base_recur($_, \$bill); - my $renew_date = $_->part_pkg->add_freq($_->bill); - { - 'pkgnum' => $_->pkgnum, - 'amount' => sprintf('%.2f', $total), - 'bill_date' => $_->bill, - 'bill_date_pretty' => time2str('%x', $_->bill), - 'renew_date' => $renew_date, - 'renew_date_pretty' => time2str('%x', $renew_date), - 'expire_date' => $_->expire, - 'expire_date_pretty' => time2str('%x', $_->expire), - }; - } - @cust_pkg; - - return { 'dates' => \@array }; - -} - -sub payment_info_renew_info { - my $p = shift; - my $renew_info = renew_info($p); - my $payment_info = payment_info($p); - return { %$renew_info, - %$payment_info, - }; -} - -sub order_renew { - my $p = shift; - - my($context, $session, $custnum) = _custoragent_session_custnum($p); - return { 'error' => $session } if $context eq 'error'; - - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or return { 'error' => "unknown custnum $custnum" }; - - my $date = $p->{'date'}; - - my $now = time; - - #freeside-daily -n -d $date fs_daily $custnum - $cust_main->bill_and_collect( 'time' => $date, - 'invoice_time' => $now, - 'actual_time' => $now, - 'check_freq' => '1d', - ); - - return { 'error' => '' }; - -} - -sub suspend_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 $conf = new FS::Conf; - my $reasonnum = - $conf->config('selfservice-self_suspend_reason', $cust_main->agentnum) - or return { 'error' => 'Permission denied' }; - - my $pkgnum = $p->{'pkgnum'}; - - my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum, - 'pkgnum' => $pkgnum, } ) - or return { 'error' => "unknown pkgnum $pkgnum" }; - - my $error = $cust_pkg->suspend(reason => $reasonnum); - return { 'error' => $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_phone { - my $p = shift; - my @bulkdid; - @bulkdid = @{$p->{'bulkdid'}} if $p->{'bulkdid'}; - -# single DID LNP - unless($p->{'lnp'}) { - $p->{'lnp_desired_due_date'} = parse_datetime($p->{'lnp_desired_due_date'}); - $p->{'lnp_status'} = "portingin"; - return _provision( 'FS::svc_phone', - [qw(lnp_desired_due_date lnp_other_provider - lnp_other_provider_account phonenum countrycode lnp_status)], - [qw(phonenum countrycode)], - $p, - @_ - ); - } - -# single DID order - unless (scalar(@bulkdid)) { - return _provision( 'FS::svc_phone', - [qw(phonenum countrycode)], - [qw(phonenum countrycode)], - $p, - @_ - ); - } - -# bulk DID order case - my $error; - foreach my $did ( @bulkdid ) { - $did =~ s/[^0-9]//g; - $error = _provision( 'FS::svc_phone', - [qw(phonenum countrycode)], - [qw(phonenum countrycode)], - { - 'pkgnum' => $p->{'pkgnum'}, - 'svcpart' => $p->{'svcpart'}, - 'phonenum' => $did, - 'countrycode' => $p->{'countrycode'}, - 'session_id' => $p->{'session_id'}, - } - ); - return $error if ($error->{'error'} && length($error->{'error'}) > 1); - } - { 'bulkdid' => [ @bulkdid ], 'svc' => $error->{'svc'} } -} - -sub provision_acct { - my $p = shift; - warn "provision_acct called\n" - if $DEBUG; - - 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'}}); - } - - warn "provision_acct calling _provision\n" - if $DEBUG; - _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); - warn "_provision called for $class\n" - if $DEBUG; - - 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'}; - - warn "searching for custnum $custnum pkgnum $pkgnum\n" - if $DEBUG; - my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum, - 'pkgnum' => $pkgnum, - } ) - or return { 'error' => "unknown pkgnum $pkgnum" }; - - warn "searching for svcpart ". $p->{'svcpart'}. "\n" - if $DEBUG; - my $part_svc = qsearchs('part_svc', { 'svcpart' => $p->{'svcpart'} } ) - or return { 'error' => "unknown svcpart $p->{'svcpart'}" }; - - warn "creating $class record\n" - if $DEBUG; - my $svc_x = $class->new( { - 'pkgnum' => $p->{'pkgnum'}, - 'svcpart' => $p->{'svcpart'}, - map { $_ => $p->{$_} } @$fields - } ); - warn "inserting $class record\n" - if $DEBUG; - my $error = $svc_x->insert; - - unless ( $error ) { - warn "finding inserted record for svcnum ". $svc_x->svcnum. "\n" - if $DEBUG; - $svc_x = qsearchs($svc_x->table, { 'svcnum' => $svc_x->svcnum }) - } - - my $return = { 'svc' => $part_svc->svc, - 'error' => $error, - map { $_ => $svc_x->get($_) } @$return_fields - }; - warn "_provision returning ". Dumper($return). "\n" - if $DEBUG; - return $return; - -} - -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 create_ticket { - my $p = shift; - my($context, $session, $custnum) = _custoragent_session_custnum($p); - return { 'error' => $session } if $context eq 'error'; - - warn "$me create_ticket: initializing ticket system\n" if $DEBUG; - FS::TicketSystem->init(); - - my $conf = new FS::Conf; - my $queue = $p->{'queue'} - || $conf->config('ticket_system-selfservice_queueid') - || $conf->config('ticket_system-default_queueid'); - - warn "$me create_ticket: creating ticket\n" if $DEBUG; - my $err_or_ticket = FS::TicketSystem->create_ticket( - '', #create RT session based on FS CurrentUser (fs_selfservice) - 'queue' => $queue, - 'custnum' => $custnum, - 'svcnum' => $session->{'svcnum'}, - map { $_ => $p->{$_} } qw( requestor cc subject message mime_type ) - ); - - if ( ref($err_or_ticket) ) { - warn "$me create_ticket: sucessful: ". $err_or_ticket->id. "\n" - if $DEBUG; - return { 'error' => '', - 'ticket_id' => $err_or_ticket->id, - }; - } else { - warn "$me create_ticket: unsucessful: $err_or_ticket\n" - if $DEBUG; - return { 'error' => $err_or_ticket }; - } - - -} - -sub did_report { - my $p = shift; - my($context, $session, $custnum) = _custoragent_session_custnum($p); - return { 'error' => $session } if $context eq 'error'; - - return { error => 'requested format not implemented' } - unless ($p->{'format'} eq 'csv' || $p->{'format'} eq 'xls'); - - my $conf = new FS::Conf; - my $age_threshold = 0; - $age_threshold = time() - $conf->config('selfservice-recent-did-age') - if ($p->{'recentonly'} && $conf->exists('selfservice-recent-did-age')); - - 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" }; - -# does it make more sense to just run one sql query for this instead of all the -# insanity below? would increase performance greately for large data sets? - my @svc_phone = (); - foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) { - my @part_svc = $cust_pkg->part_svc; - foreach my $part_svc ( @part_svc ) { - if($part_svc->svcdb eq 'svc_phone'){ - my @cust_pkg_svc = @{$part_svc->cust_pkg_svc}; - foreach my $cust_pkg_svc ( @cust_pkg_svc ) { - push @svc_phone, $cust_pkg_svc->svc_x - if $cust_pkg_svc->date_inserted >= $age_threshold; - } - } - } - } - - my $csv; - my $xls; - my($xls_r,$xls_c) = (0,0); - my $xls_workbook; - my $content = ''; - my @fields = qw( countrycode phonenum pin sip_password phone_name ); - if($p->{'format'} eq 'csv') { - $csv = new Text::CSV_XS { 'always_quote' => 1, - 'eol' => "\n", - }; - return { 'error' => 'Unable to create CSV' } unless $csv->combine(@fields); - $content .= $csv->string; - } - elsif($p->{'format'} eq 'xls') { - my $XLS1 = new IO::Scalar \$content; - $xls_workbook = Spreadsheet::WriteExcel->new($XLS1) - or return { 'error' => "Error opening .xls file: $!" }; - $xls = $xls_workbook->add_worksheet('DIDs'); - foreach ( @fields ) { - $xls->write(0,$xls_c++,$_); - } - $xls_r++; - } - - foreach my $svc_phone ( @svc_phone ) { - my @cols = map { $svc_phone->$_ } @fields; - if($p->{'format'} eq 'csv') { - return { 'error' => 'Unable to create CSV' } - unless $csv->combine(@cols); - $content .= $csv->string; - } - elsif($p->{'format'} eq 'xls') { - $xls_c = 0; - foreach ( @cols ) { - $xls->write($xls_r,$xls_c++,$_); - } - $xls_r++; - } - } - - $xls_workbook->close() if $p->{'format'} eq 'xls'; - - { content => $content, format => $p->{'format'}, }; -} - -sub get_ticket { - my $p = shift; - my($context, $session, $custnum) = _custoragent_session_custnum($p); - return { 'error' => $session } if $context eq 'error'; - - warn "$me get_ticket: initializing ticket system\n" if $DEBUG; - FS::TicketSystem->init(); - - if(length($p->{'reply'})) { -# currently this allows anyone to correspond on any ticket as fs_selfservice -# probably bad... - my @err_or_res = FS::TicketSystem->correspond_ticket( - '', #create RT session based on FS CurrentUser (fs_selfservice) - 'ticket_id' => $p->{'ticket_id'}, - 'content' => $p->{'reply'}, - ); - - return { 'error' => 'unable to reply to ticket' } - unless ( $err_or_res[0] != 0 && defined $err_or_res[2] ); - } - - warn "$me get_ticket: getting ticket\n" if $DEBUG; - my $err_or_ticket = FS::TicketSystem->get_ticket( - '', #create RT session based on FS CurrentUser (fs_selfservice) - 'ticket_id' => $p->{'ticket_id'}, - ); - - if ( ref($err_or_ticket) ) { - -# since we're bypassing the RT security/permissions model by always using -# fs_selfservice as the RT user (as opposed to a requestor, which we -# can't do since we want all tickets linked to a cust), we check below whether -# the requested ticket was actually linked to this customer - my @custs = @{$err_or_ticket->{'custs'}}; - my @txns = @{$err_or_ticket->{'txns'}}; - my @filtered_txns; - - return { 'error' => 'no customer' } unless ( $custnum && scalar(@custs) ); - - return { 'error' => 'invalid ticket requested' } - unless grep($_ eq $custnum, @custs); - - foreach my $txn ( @txns ) { - push @filtered_txns, $txn - if ($txn->{'type'} eq 'EmailRecord' - || $txn->{'type'} eq 'Correspond' - || $txn->{'type'} eq 'Create'); - } - - warn "$me get_ticket: sucessful: \n" - if $DEBUG; - return { 'error' => '', - 'transactions' => \@filtered_txns, - 'ticket_id' => $p->{'ticket_id'}, - }; - } else { - warn "$me create_ticket: unsucessful: $err_or_ticket\n" - if $DEBUG; - return { 'error' => $err_or_ticket }; - } -} - - -#-- - -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 { - $context = 'error'; - return ( 'error' => "Can't resume session" ); #better error message - } - - ($context, $session, $custnum); - -} - -1; - diff --git a/FS/FS/ClientAPI/PrepaidPhone.pm b/FS/FS/ClientAPI/PrepaidPhone.pm deleted file mode 100644 index 00bc0ffd0..000000000 --- a/FS/FS/ClientAPI/PrepaidPhone.pm +++ /dev/null @@ -1,253 +0,0 @@ -package FS::ClientAPI::PrepaidPhone; - -use strict; -use vars qw($DEBUG $me); -use FS::Record qw(qsearchs); -use FS::rate; -use FS::svc_phone; - -$DEBUG = 0; -$me = '[FS::ClientAPI::PrepaidPhone]'; - -#TODO: -# - shared-secret auth? (set a conf value) - -=item call_time HASHREF - -HASHREF contains the following parameters: - -=over 4 - -=item src - -Source number (with countrycode) - -=item dst - -Destination number (with countrycode) - -=back - -Always returns a hashref. If there is an error, the hashref contains a single -"error" key with the error message as a value. Otherwise, returns a hashref -with the following keys: - -=over 4 - -=item custnum - -Empty if no customer is found associated with the number, customer number -otherwise. - -=item seconds - -Number of seconds remaining for a call to destination number - -=back - -=cut - -sub call_time { - my $packet = shift; - - my $src = $packet->{'src'}; - my $dst = $packet->{'dst'}; - - my $chargeto; - my $rateby; - #my $conf = new FS::Conf; - #if ( #XXX toll-free? collect? - # $phonenum = $dst; - #} else { #use the src to find the customer - $chargeto = $src; - $rateby = $dst; - #} - - my( $countrycode, $phonenum ); - if ( $chargeto #an interesting regex to parse out 1&2 digit countrycodes - =~ /^(2[078]|3[0-469]|4[013-9]|5[1-8]|6[0-6]|7|8[1-469]|9[0-58])(\d*)$/ - || $chargeto =~ /^(\d{3})(\d*)$/ - ) - { - $countrycode = $1; - $phonenum = $2; - } else { - return { 'error' => "unparsable billing number: $chargeto" }; - } - - - my $svc_phone = qsearchs('svc_phone', { 'countrycode' => $countrycode, - 'phonenum' => $phonenum, - } - ); - - unless ( $svc_phone ) { - return { 'error' => "can't find customer for +$countrycode $phonenum" }; -# return { 'custnum' => '', -# 'seconds' => 0, -# #'balance' => 0, -# }; - }; - - my $cust_pkg = $svc_phone->cust_svc->cust_pkg; - my $cust_main = $cust_pkg->cust_main; - - my $part_pkg = $cust_pkg->part_pkg; - my @part_pkg = ( $part_pkg, map $_->dst_pkg, $part_pkg->bill_part_pkg_link ); - #XXX uuh, behavior indeterminate if you have more than one voip_cdr+prefix - #add-on, i guess. - warn "$me ". scalar(@part_pkg). ': '. - join('/', map { $_->plan. $_->option('rating_method') } @part_pkg ) - if $DEBUG; - @part_pkg = - grep { $_->plan eq 'voip_cdr' && $_->option('rating_method') eq 'prefix' } - @part_pkg; - - my %return = ( - 'custnum' => $cust_pkg->custnum, - #'balance' => $cust_pkg->cust_main->balance, - ); - - warn "$me: ". scalar(@part_pkg). ': '. - join('/', map { $_->plan. $_->option('rating_method') } @part_pkg ) - if $DEBUG; - return \%return unless @part_pkg; - - warn "$me searching for rate ". $part_pkg[0]->option('ratenum') - if $DEBUG; - - my $rate = qsearchs('rate', { 'ratenum'=>$part_pkg[0]->option('ratenum') } ); - - unless ( $rate ) { - my $error = 'ratenum '. $part_pkg[0]->option('ratenum'). ' not found'; - warn "$me $error" - if $DEBUG; - return { 'error'=>$error }; - } - - warn "$me found rate ". $rate->ratenum - if $DEBUG; - - #rate the call and arrive at a max # of seconds for the customer's balance - - my( $rate_countrycode, $rate_phonenum ); - if ( $rateby #this is an interesting regex to parse out 1&2 digit countrycodes - =~ /^(2[078]|3[0-469]|4[013-9]|5[1-8]|6[0-6]|7|8[1-469]|9[0-58])(\d*)$/ - || $rateby =~ /^(\d{3})(\d*)$/ - ) - { - $rate_countrycode = $1; - $rate_phonenum = $2; - } else { - return { 'error' => "unparsable rating number: $rateby" }; - } - - my $rate_detail = $rate->dest_detail({ 'countrycode' => $rate_countrycode, - 'phonenum' => $rate_phonenum, - }); - unless ( $rate_detail ) { - return { 'error'=>"can't find rate for +$rate_countrycode $rate_phonenum"}; - } - - unless ( $rate_detail->min_charge > 0 ) { - #XXX no charge?? return lots of seconds, a default, 0 or what? - #return { 'error' => '0 rate for +$rate_countrycode $rate_phonenum; prepaid service not available" }; - #customer wants no default for now# $return{'seconds'} = 1800; #half hour?! - return \%return; - } - - #XXX granularity? included minutes? another day... - if ( $cust_main->balance >= 0 ) { - return { 'error'=>'No balance' }; - } else { - $return{'seconds'} = int(60 * abs($cust_main->balance) / $rate_detail->min_charge); - } - - warn "$me returning seconds: ". $return{'seconds'}; - - return \%return; - -} - -=item call_time_nanpa - -Like I, except countrycode 1 is not required, and all other -countrycodes must be prefixed with 011. - -=cut - -# - everything is assumed to be countrycode 1 unless it starts with 011(ccode) -sub call_time_nanpa { - my $packet = shift; - - foreach (qw( src dst )) { - if ( $packet->{$_} =~ /^011(\d+)/ ) { - $packet->{$_} = $1; - } elsif ( $packet->{$_} !~ /^1/ ) { - $packet->{$_} = '1'.$packet->{$_}; - } - } - - call_time($packet); - -} - -=item phonenum_balance HASHREF - -HASHREF contains the following parameters: - -=over 4 - -=item countrycode - -Optional countrycode. Defaults to 1. - -=item phonenum - -Phone number. - -=back - -Always returns a hashref. If there is an error, the hashref contains a single -"error" key with the error message as a value. Otherwise, returns a hashref -with the following keys: - -=over 4 - -=item custnum - -Empty if no customer is found associated with the number, customer number -otherwise. - -=item balance - -Customer balance. - -=back - -=cut - -sub phonenum_balance { - my $packet = shift; - - my $svc_phone = qsearchs('svc_phone', { - 'countrycode' => ( $packet->{'countrycode'} || 1 ), - 'phonenum' => $packet->{'phonenum'}, - }); - - unless ( $svc_phone ) { - return { 'custnum' => '', - 'balance' => 0, - }; - }; - - my $cust_pkg = $svc_phone->cust_svc->cust_pkg; - - return { - 'custnum' => $cust_pkg->custnum, - 'balance' => $cust_pkg->cust_main->balance, - }; - -} - -1; diff --git a/FS/FS/ClientAPI/SGNG.pm b/FS/FS/ClientAPI/SGNG.pm deleted file mode 100644 index 7f784dcd0..000000000 --- a/FS/FS/ClientAPI/SGNG.pm +++ /dev/null @@ -1,277 +0,0 @@ -#this stuff is SG-specific (i.e. multi-customer company username hack) - -package FS::ClientAPI::SGNG; - -use strict; -use vars qw( $cache $DEBUG ); -use Time::Local qw(timelocal timelocal_nocheck); -use Business::CreditCard; -use FS::Record qw( qsearch qsearchs ); -use FS::Conf; -use FS::cust_main; -use FS::cust_pkg; -use FS::ClientAPI::MyAccount; #qw( payment_info process_payment ) - -$DEBUG = 0; - -sub _cache { - $cache ||= new FS::ClientAPI_SessionCache( { - 'namespace' => 'FS::ClientAPI::MyAccount', #yes, share session_ids - } ); -} - -sub ping { - #my $p = shift; - - return { 'pong' => '1' }; - -} - -#this might almost be general-purpose -sub decompify_pkgs { - 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" }; - - return { 'error' => 'Not a complimentary customer' } - unless $cust_main->payby eq 'COMP'; - - my $paydate = - $cust_main->paydate =~ /^\S+$/ ? $cust_main->paydate : '2037-12-31'; - - my ($payyear,$paymonth,$payday) = split (/-/,$paydate); - - my $date = timelocal(0,0,0,$payday,--$paymonth,$payyear); - - foreach my $cust_pkg ( - qsearch({ 'table' => 'cust_pkg', - 'hashref' => { 'custnum' => $custnum, - 'bill' => '', - }, - 'extra_sql' => ' AND '. FS::cust_pkg->active_sql, - }) - ) { - $cust_pkg->set('bill', $date); - my $error = $cust_pkg->replace; - return { 'error' => $error } if $error; - } - - return { 'error' => '' }; - -} - -#find old payment info -# (should work just like MyAccount::payment_info, except returns previous info -# too) -# definitly sg-specific, no one else stores past customer records like this -sub previous_payment_info { - my $p = shift; - - my $session = _cache->get($p->{'session_id'}) - or return { 'error' => "Can't resume session" }; #better error message - - my $payment_info = FS::ClientAPI::MyAccount::payment_info($p); - - my $custnum = $session->{'custnum'}; - - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or return { 'error' => "unknown custnum $custnum" }; - - #? - return $payment_info if $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/; - - foreach my $prev_cust_main ( - reverse _previous_cust_main( 'custnum' => $custnum, - 'username' => $cust_main->company, - 'with_payments' => 1, - ) - ) { - - next unless $prev_cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/; - - if ( $prev_cust_main->payby =~ /^(CARD|DCRD)$/ ) { - - #card expired? - my ($payyear,$paymonth,$payday) = split (/-/, $cust_main->paydate); - - my $expdate = timelocal_nocheck(0,0,0,1,$paymonth,$payyear); - - next if $expdate < time; - - } elsif ( $prev_cust_main->payby =~ /^(CHEK|DCHK)$/ ) { - - #any check? or just skip these in favor of cards? - - } - - return { %$payment_info, - #$prev_cust_main->payment_info - _cust_main_payment_info( $prev_cust_main ), - 'previous_custnum' => $prev_cust_main->custnum, - }; - - } - - #still nothing? return an error? - return $payment_info; - -} - -#this is really FS::cust_main::payment_info, but here for now -sub _cust_main_payment_info { - my $self = shift; - - my %return = (); - - $return{balance} = $self->balance; - - $return{payname} = $self->payname - || ( $self->first. ' '. $self->get('last') ); - - $return{$_} = $self->get($_) for qw(address1 address2 city state zip); - - $return{payby} = $self->payby; - $return{stateid_state} = $self->stateid_state; - - if ( $self->payby =~ /^(CARD|DCRD)$/ ) { - $return{card_type} = cardtype($self->payinfo); - $return{payinfo} = $self->paymask; - - @return{'month', 'year'} = $self->paydate_monthyear; - - } - - if ( $self->payby =~ /^(CHEK|DCHK)$/ ) { - my ($payinfo1, $payinfo2) = split '@', $self->paymask; - $return{payinfo1} = $payinfo1; - $return{payinfo2} = $payinfo2; - $return{paytype} = $self->paytype; - $return{paystate} = $self->paystate; - - } - - #doubleclick protection - my $_date = time; - $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32; - - %return; - -} - -#find old cust_main records (with payments) -sub _previous_cust_main { - - #safety check! return nothing unless we're enabled explicitly - return () unless FS::Conf->new->exists('sg-multicustomer_hack'); - - my %opt = @_; - my $custnum = $opt{'custnum'}; - my $username = $opt{'username'}; - - my %search = (); - if ( $opt{'with_payments'} ) { - $search{'extra_sql'} = - ' AND 0 < ( SELECT COUNT(*) FROM cust_pay - WHERE cust_pay.custnum = cust_main.custnum - ) - '; - } - - qsearch( { - 'table' => 'cust_main', - 'hashref' => { 'company' => { op => 'ILIKE', value => $opt{'username'} }, - 'custnum' => { op => '!=', value => $opt{'custnum'} }, - }, - 'order_by' => 'ORDER BY custnum', - %search, - } ); - -} - -#since we could be passing masked old CC data, need to look that up and -#replace it (like regular process_payment does) w/info from old customer record -sub previous_process_payment { - my $p = shift; - - return FS::ClientAPI::MyAccount::process_payment($p) - unless $p->{'previous_custnum'} - && ( ( $p->{'payby'} =~ /^(CARD|DCRD)$/ && $p->{'payinfo'} =~ /x/i ) - || ( $p->{'payby'} =~ /^(CHEK|DCHK)$/ && $p->{'payinfo1'} =~ /x/i ) - ); - - 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" }; - - #make sure this is really a previous custnum of this customer - my @previous_cust_main = - grep { $_->custnum == $p->{'previous_custnum'} } - _previous_cust_main( 'custnum' => $custnum, - 'username' => $cust_main->company, - 'with_payments' => 1, - ); - - my $previous_cust_main = $previous_cust_main[0]; - - #causes problems with old data w/old masking method - #if $previous_cust_main->paymask eq $payinfo; - - if ( $p->{'payby'} =~ /^(CHEK|DCHK)$/ && $p->{'payinfo1'} =~ /x/i ) { - ( $p->{'payinfo1'}, $p->{'payinfo2'} ) = - split('@', $previous_cust_main->payinfo); - } elsif ( $p->{'payby'} =~ /^(CARD|DCRD)$/ && $p->{'payinfo'} =~ /x/i ) { - $p->{'payinfo'} = $previous_cust_main->payinfo; - } - - FS::ClientAPI::MyAccount::process_payment($p); - -} - -sub previous_payment_info_renew_info { - my $p = shift; - my $renew_info = renew_info($p); - my $payment_info = previous_payment_info($p); - return { %$renew_info, - %$payment_info, - }; -} - -sub previous_process_payment_order_pkg { - my $p = shift; - - my $hr = previous_process_payment($p); - return $hr if $hr->{'error'}; - - order_pkg($p); -} - -sub previous_process_payment_change_pkg { - my $p = shift; - - my $hr = previous_process_payment($p); - return $hr if $hr->{'error'}; - - change_pkg($p); -} - -sub previous_process_payment_order_renew { - my $p = shift; - - my $hr = previous_process_payment($p); - return $hr if $hr->{'error'}; - - order_renew($p); -} - -1; - diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm deleted file mode 100644 index 488692f9f..000000000 --- a/FS/FS/ClientAPI/Signup.pm +++ /dev/null @@ -1,888 +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::CGI qw(popurl); -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::svc_phone; -use FS::acct_snarf; -use FS::queue; -use FS::reg_code; -use FS::payby; - -$DEBUG = 0; -$me = '[FS::ClientAPI::Signup]'; - -sub clear_cache { - warn "$me clear_cache called\n" if $DEBUG; - my $cache = new FS::ClientAPI_SessionCache( { - 'namespace' => 'FS::ClientAPI::Signup', - } ); - $cache->clear(); - return {}; -} - -sub signup_info { - my $packet = shift; - - warn "$me signup_info called on $packet\n" if $DEBUG; - - my $conf = new FS::Conf; - my $svc_x = $conf->config('signup_server-service') || 'svc_acct'; - - 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 $agent = $_; - my $href = $agent->pkgpart_hashref; - $agent->agentnum => - [ - map { { 'payby' => [ $_->payby ], - 'freq_pretty' => $_->freq_pretty, - 'options' => { $_->options }, - %{$_->hashref} - } } - grep { $_->svcpart($svc_x) - && ( $href->{ $_->pkgpart } - || ( $_->agentnum - && $_->agentnum == $agent->agentnum - ) - ) - } - 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; - - my @agent_fields = qw( agentnum agent ); - - my @bools = qw( emailinvoiceonly security_phrase ); - - my @signup_bools = qw( no_company recommend_daytime recommend_email ); - - my @signup_server_scalars = qw( default_pkgpart default_svcpart ); - - my @selfservice_textareas = qw( head body_header body_footer ); - - my @selfservice_scalars = qw( - body_bgcolor box_bgcolor - text_color link_color vlink_color hlink_color alink_color - font title_color title_align title_size menu_bgcolor menu_fontsize - ); - - #XXX my @selfservice_bools = qw( - # menu_skipblanks menu_skipheadings menu_nounderline - #); - - #my $selfservice_binaries = qw( - # title_left_image title_right_image - # menu_top_image menu_body_image menu_bottom_image - #); - - $signup_info_cache = { - - 'cust_main_county' => [ map $_->hashref, - qsearch('cust_main_county', {} ) - ], - - 'agent' => [ map { my $agent = $_; - +{ map { $_ => $agent->get($_) } @agent_fields } - } - 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'), - - 'nomadix' => $conf->exists('signup_server-nomadix'), - - 'payby' => [ $conf->config('signup_server-payby') ], - - 'payby_longname' => [ map { FS::payby->longname($_) } - $conf->config('signup_server-payby') ], - - 'card_types' => card_types(), - - ( map { $_ => $conf->exists("signup-$_") } @signup_bools ), - - ( map { $_ => scalar($conf->config("signup_server-$_")) } - @signup_server_scalars - ), - - ( map { $_ => join("\n", $conf->config("selfservice-$_")) } - @selfservice_textareas - ), - ( map { $_ => scalar($conf->config("selfservice-$_")) } - @selfservice_scalars - ), - - #( map { $_ => scalar($conf->config_binary("selfservice-$_")) } - # @selfservice_binaries - #), - - 'agentnum2part_pkg' => $agentnum2part_pkg, - 'svc_acct_pop' => [ map $_->hashref, qsearch('svc_acct_pop',{} ) ], - 'nomadix' => $conf->exists('signup_server-nomadix'), - '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')), - 'signup_service' => $svc_x, - 'company_name' => scalar($conf->config('company_name')), - #per-agent? - 'agent_ship_address' => scalar($conf->exists('agent-ship_address')), - 'require_phone' => scalar($conf->exists('cust_main-require_phone')), - 'logo' => scalar($conf->config_binary('logo.png')), - - }; - - $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_x) } - 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_x) } - 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 payment flag\n" if $DEBUG > 1; - my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); - warn "$me has agent $agent\n" if $DEBUG > 1; - if ( $agent ) { #else complain loudly? - $signup_info->{'hide_payment_fields'} = []; - my $gatewaynum = $conf->config('selfservice-payment_gateway'); - if ( $gatewaynum ) { - my $pg = qsearchs('payment_gateway', { gatewaynum => $gatewaynum }); - die "configured gatewaynum $gatewaynum not found!" if !$pg; - my $hide = $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment'; - $signup_info->{'hide_payment_fields'} = [ - map { $hide } @{$signup_info->{'payby'}} - ]; - } - else { - foreach my $payby (@{$signup_info->{payby}}) { - warn "$me checking $payby payment fields\n" if $DEBUG > 1; - my $hide = 0; - if ( FS::payby->realtime($payby) ) { - my $payment_gateway = - $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby), - 'nofatal' => 1, - ); - if ( $payment_gateway - && $payment_gateway->gateway_namespace - eq 'Business::OnlineThirdPartyPayment' - ) { - warn "$me hiding $payby payment fields\n" if $DEBUG > 1; - $hide = 1; - } - } - push @{$signup_info->{'hide_payment_fields'}}, $hide; - } # foreach $payby - } - } - warn "$me done setting agent-specific payment flag\n" if $DEBUG > 1; - - 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; - - $signup_info->{'agent_name'} = $agent->agent; - - $signup_info->{'company_name'} = $conf->config('company_name', $agentnum); - - if ( $signup_info->{'agent_ship_address'} && $agent->agent_custnum ) { - my $cust_main = $agent->agent_cust_main; - my $prefix = length($cust_main->ship_last) ? 'ship_' : ''; - $signup_info->{"ship_$_"} = $cust_main->get("$prefix$_") - foreach qw( address1 city county state zip country ); - } - - #some of the above could probably be cached, too - - my $signup_info_cache_agent = $cache->get("signup_info_cache_agent$agentnum"); - - if ( $signup_info_cache_agent ) { - - warn "$me loading cached signup info for agentnum $agentnum\n" - if $DEBUG > 1; - - } else { - - warn "$me populating signup info cache for agentnum $agentnum\n" - if $DEBUG > 1; - - $signup_info_cache_agent = { - #( map { $_ => scalar( $conf->config($_, $agentnum) ) } - # qw( company_name ) ), - ( map { $_ => scalar( $conf->config("selfservice-$_", $agentnum ) ) } - qw( body_bgcolor box_bgcolor menu_bgcolor ) ), - ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) } - qw( head body_header body_footer ) ), - }; - - $cache->set("signup_info_cache_agent$agentnum", $signup_info_cache_agent); - - } - - $signup_info->{$_} = $signup_info_cache_agent->{$_} - foreach keys %$signup_info_cache_agent; - - } - # 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'}; - return $agent_signup_info; - } - elsif ( exists $packet->{'keys'} ) { - my @keys = @{ $packet->{'keys'} }; - return { map { $_ => $signup_info->{$_} } @keys }; - } - else { - return $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; - my $svc_x = $conf->config('signup_server-service') || 'svc_acct'; - - if ( $svc_x eq 'svc_acct' ) { - - #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',{} )); - - } - elsif ( $svc_x eq 'svc_pbx' ) { - #possibly some validation will be needed - } - - 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 - ) - - } ); - - my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); - if ( $conf->exists('agent_ship_address') && $agent->agent_custnum ) { - my $agent_cust_main = $agent->agent_cust_main; - my $prefix = length($agent_cust_main->ship_last) ? 'ship_' : ''; - $cust_main->set("ship_$_", $agent_cust_main->get("$prefix$_") ) - foreach qw( address1 city county state zip country ); - - $cust_main->set("ship_$_", $cust_main->get($_)) - foreach qw( last first ); - - } - - - return { 'error' => "Illegal payment type" } - unless grep { $_ eq $packet->{'payby'} } - $conf->config('signup_server-payby'); - - if (FS::payby->realtime($packet->{payby})) { - my $payby = $packet->{payby}; - - my $agent = qsearchs('agent', { 'agentnum' => $agentnum }); - return { 'error' => "Unknown reseller" } - unless $agent; - - my $gw; - my $gatewaynum = $conf->config('selfservice-payment_gateway'); - if ( $gatewaynum ) { - $gw = qsearchs('payment_gateway', { gatewaynum => $gatewaynum }); - die "configured gatewaynum $gatewaynum not found!" if !$gw; - } - else { - $gw = $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby), - 'nofatal' => 1, - ); - } - - $cust_main->payby('BILL') # MCRD better? - if $gw && $gw->gateway_namespace eq 'Business::OnlineThirdPartyPayment'; - } - - $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_x); - - 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; - - #should be all auto-magic and shit - my @svc = (); - if ( $svc_x eq 'svc_acct' ) { - - my $svc = 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->child_objects( \@acct_snarf ); - push @svc, $svc; - - } elsif ( $svc_x eq 'svc_phone' ) { - - push @svc, new FS::svc_phone ( { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( countrycode phonenum sip_password pin ), - } ); - - } elsif ( $svc_x eq 'svc_pbx' ) { - - push @svc, new FS::svc_pbx ( { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( id title ), - } ); - - } else { - die "unknown signup service $svc_x"; - } - - if ($packet->{'mac_addr'} && $conf->exists('signup_server-mac_addr_svcparts')) - { - - my %mac_addr_svcparts = map { $_ => 1 } - $conf->config('signup_server-mac_addr_svcparts'); - my @pkg_svc = grep { $_->quantity && $mac_addr_svcparts{$_->svcpart} } - $cust_pkg->part_pkg->pkg_svc; - - return { 'error' => 'No service defined to assign mac address' } - unless @pkg_svc; - - my $svc = new FS::svc_acct { - 'svcpart' => $pkg_svc[0]->svcpart, #multiple matches? alas.. - 'username' => $packet->{'mac_addr'}, - '_password' => '', #blank as requested (set passwordmin to 0) - }; - - push @svc, $svc; - - } - - foreach my $svc ( @svc ) { - my $y = $svc->setdefault; # arguably should be in new method - return { 'error' => $y } if $y && !ref($y); - #$error = $svc->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 ); - #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 "$me Billing customer...\n" if $Debug; - - my $bill_error = $cust_main->bill; - #warn "$me error billing new customer: $bill_error" - # if $bill_error; - - $bill_error = $cust_main->apply_payments_and_credits; - #warn "$me error applying payments and credits for". - # " new customer: $bill_error" - # if $bill_error; - - $bill_error = $cust_main->realtime_collect( - method => FS::payby->payby2bop( $packet->{payby} ), - depend_jobnum => $placeholder->jobnum, - selfservice => 1, - ); - #warn "$me error collecting from new customer: $bill_error" - # if $bill_error; - - if ($bill_error && ref($bill_error) eq 'HASH') { - return { 'error' => '_collect', - ( map { $_ => $bill_error->{$_} } - qw(popup_url reference collectitems) - ), - amount => $cust_main->balance, - }; - } - - $bill_error = $cust_main->apply_payments_and_credits; - #warn "$me error applying payments and credits for". - # " 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; - - my %return = ( 'error' => '', - 'signup_service' => $svc_x, - 'custnum' => $cust_main->custnum, - ); - - if ( $svc[0] ) { - - $return{'svcnum'} = $svc[0]->svcnum; - - if ( $svc_x eq 'svc_acct' ) { - $return{$_} = $svc[0]->$_() for qw( username _password ); - } elsif ( $svc_x eq 'svc_phone' ) { - $return{$_} = $svc[0]->$_() for qw(countrycode phonenum sip_password pin); - } elsif ( $svc_x eq 'svc_pbx' ) { - #$return{$_} = $svc[0]->$_() for qw( ) #nothing yet - } else { - return {'error' => "configuration error: unknown signup service $svc_x"}; - #die "unknown signup service $svc_x"; - # return an error that's visible to someone somewhere - } - - } - - return \%return; - -} - -sub capture_payment { - my $packet = shift; - - warn "$me capture_payment called on $packet\n" if $DEBUG; - - ### - # identify processor/gateway from called back URL - ### - - my $conf = new FS::Conf; - - my $payment_gateway; - if ( my $gwnum = $conf->config('selfservice-payment_gateway') ) { - $payment_gateway = qsearchs('payment_gateway', { 'gatewaynum' => $gwnum }) - or die "configured gatewaynum $gwnum not found!"; - } - else { - my $url = $packet->{url}; - - $payment_gateway = qsearchs('payment_gateway', - { 'gateway_callback_url' => popurl(0, $url) } - ); - if (!$payment_gateway) { - - my ( $processor, $login, $password, $action, @bop_options ) = - $conf->config('business-onlinepayment'); - $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; - - $payment_gateway = new FS::payment_gateway( { - gateway_namespace => $conf->config('business-onlinepayment-namespace'), - gateway_module => $processor, - gateway_username => $login, - gateway_password => $password, - gateway_action => $action, - options => [ ( @bop_options ) ], - }); - } - } - - die "No real-time third party processor is enabled - ". - "did you set the business-onlinepayment configuration value?\n*" - unless $payment_gateway->gateway_namespace eq 'Business::OnlineThirdPartyPayment'; - - ### - # locate pending transaction - ### - - eval "use Business::OnlineThirdPartyPayment"; - die $@ if $@; - - my $transaction = - new Business::OnlineThirdPartyPayment( $payment_gateway->gateway_module, - @{ [ $payment_gateway->options ] }, - ); - - my $paypendingnum = $transaction->reference($packet->{data}); - - my $cust_pay_pending = - qsearchs('cust_pay_pending', { paypendingnum => $paypendingnum } ); - - unless ($cust_pay_pending) { - my $bill_error = "No payment is being processed with id $paypendingnum". - "; Transaction aborted."; - return { error => '_decline', bill_error => $bill_error }; - } - - if ($cust_pay_pending->status ne 'pending') { - my $bill_error = "Payment with id $paypendingnum is not pending, but ". - $cust_pay_pending->status. "; Transaction aborted."; - return { error => '_decline', bill_error => $bill_error }; - } - - my $cust_main = $cust_pay_pending->cust_main; - my $bill_error = - $cust_main->realtime_botpp_capture( $cust_pay_pending, - %{$packet->{data}}, - apply => 1, - ); - - return { 'error' => ( $bill_error->{bill_error} ? '_decline' : '' ), - %$bill_error, - }; - -} - -1; diff --git a/FS/FS/ClientAPI/passwd.pm b/FS/FS/ClientAPI/passwd.pm deleted file mode 100644 index b22d7617e..000000000 --- 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 d72fb39ce..000000000 --- a/FS/FS/ClientAPI_SessionCache.pm +++ /dev/null @@ -1,79 +0,0 @@ -package FS::ClientAPI_SessionCache; - -use strict; -use vars qw($module); -use FS::UID qw(datasrc); -use FS::Conf; - -#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, L, L - -=cut - -1; diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm deleted file mode 100644 index 48b94eba2..000000000 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ /dev/null @@ -1,148 +0,0 @@ -package FS::ClientAPI_XMLRPC; - -=head1 NAME - -FS::ClientAPI_XMLRPC - Freeside XMLRPC accessible self-service API, on the backend - -=head1 SYNOPSIS - -This module implements the self-service API offered by xmlrpc.cgi and friends, -but on a backend machine. - -=head1 DESCRIPTION - -Use this API to implement your own client "self-service" module vi XMLRPC. - -Each routine described in L is available vi XMLRPC as the -method FS.SelfService.XMLRPC.B. All values are passed to the -selfservice-server in a struct of strings. The return values are in a -struct as strings, arrays, or structs as appropriate for the values -described in L. - -=head1 BUGS - -=head1 SEE ALSO - -L, L - -=cut - -use strict; - -use vars qw($DEBUG $AUTOLOAD); -use FS::ClientAPI; - -$DEBUG = 0; -$FS::ClientAPI::DEBUG = $DEBUG; - -sub AUTOLOAD { - my $call = $AUTOLOAD; - $call =~ s/^FS::(SelfService::|ClientAPI_)XMLRPC:://; - - warn "FS::ClientAPI_XMLRPC::AUTOLOAD $call\n" if $DEBUG; - - my $autoload = &ss2clientapi; - - if (exists($autoload->{$call})) { - shift; #discard package name; - #$call = "FS::SelfService::$call"; - #no strict 'refs'; - #&{$call}(@_); - #FS::ClientAPI->dispatch($autoload->{$call}, @_); - FS::ClientAPI->dispatch($autoload->{$call}, @_ ); - }else{ - die "No such procedure: $call"; - } -} - -#terrible false laziness w/SelfService.pm -# - fix at build time, by including some file in both selfserv and backend libs? -# - or fix at runtime, by having selfservice client ask server for the list? -sub ss2clientapi { - { - 'passwd' => 'passwd/passwd', - 'chfn' => 'passwd/passwd', - 'chsh' => 'passwd/passwd', - 'login_info' => 'MyAccount/login_info', - 'login' => 'MyAccount/login', - 'logout' => 'MyAccount/logout', - 'customer_info' => 'MyAccount/customer_info', - 'edit_info' => 'MyAccount/edit_info', #add to ss cgi! - 'invoice' => 'MyAccount/invoice', - 'invoice_logo' => 'MyAccount/invoice_logo', - 'list_invoices' => 'MyAccount/list_invoices', #? - 'cancel' => 'MyAccount/cancel', #add to ss cgi! - 'payment_info' => 'MyAccount/payment_info', - 'payment_info_renew_info' => 'MyAccount/payment_info_renew_info', - 'process_payment' => 'MyAccount/process_payment', - 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg', - 'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg', - 'process_payment_order_renew' => 'MyAccount/process_payment_order_renew', - 'process_prepay' => 'MyAccount/process_prepay', - 'realtime_collect' => 'MyAccount/realtime_collect', - 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?) - 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?) - 'list_svc_usage' => 'MyAccount/list_svc_usage', - 'list_cdr_usage' => 'MyAccount/list_cdr_usage', - 'list_support_usage' => 'MyAccount/list_support_usage', - 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi! - 'change_pkg' => 'MyAccount/change_pkg', - 'order_recharge' => 'MyAccount/order_recharge', - 'renew_info' => 'MyAccount/renew_info', - 'order_renew' => 'MyAccount/order_renew', - 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi! - 'suspend_pkg' => 'MyAccount/suspend_pkg', #add to ss cgi! - 'charge' => 'MyAccount/charge', #? - 'part_svc_info' => 'MyAccount/part_svc_info', - 'provision_acct' => 'MyAccount/provision_acct', - 'provision_external' => 'MyAccount/provision_external', - 'unprovision_svc' => 'MyAccount/unprovision_svc', - 'myaccount_passwd' => 'MyAccount/myaccount_passwd', - 'create_ticket' => 'MyAccount/create_ticket', - 'signup_info' => 'Signup/signup_info', - 'skin_info' => 'MyAccount/skin_info', - 'access_info' => 'MyAccount/access_info', - 'domain_select_hash' => 'Signup/domain_select_hash', # expose? - 'new_customer' => 'Signup/new_customer', - 'capture_payment' => 'Signup/capture_payment', - 'clear_signup_cache' => 'Signup/clear_cache', - 'agent_login' => 'Agent/agent_login', - 'agent_logout' => 'Agent/agent_logout', - 'agent_info' => 'Agent/agent_info', - 'agent_list_customers' => 'Agent/agent_list_customers', - 'mason_comp' => 'MasonComponent/mason_comp', - 'call_time' => 'PrepaidPhone/call_time', - 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa', - 'phonenum_balance' => 'PrepaidPhone/phonenum_balance', - 'bulk_processrow' => 'Bulk/processrow', - 'check_username' => 'Bulk/check_username', - #sg - 'ping' => 'SGNG/ping', - 'decompify_pkgs' => 'SGNG/decompify_pkgs', - 'previous_payment_info' => 'SGNG/previous_payment_info', - 'previous_payment_info_renew_info' - => 'SGNG/previous_payment_info_renew_info', - 'previous_process_payment' => 'SGNG/previous_process_payment', - 'previous_process_payment_order_pkg' - => 'SGNG/previous_process_payment_order_pkg', - 'previous_process_payment_change_pkg' - => 'SGNG/previous_process_payment_change_pkg', - 'previous_process_payment_order_renew' - => 'SGNG/previous_process_payment_order_renew', - }; -} - - -#XXX submit patch to SOAP::Lite - -use XMLRPC::Transport::HTTP; - -package XMLRPC::Transport::HTTP::Server; - -@XMLRPC::Transport::HTTP::Server::ISA = qw(SOAP::Transport::HTTP::Server); - -sub initialize; *initialize = \&XMLRPC::Server::initialize; -sub make_fault; *make_fault = \&XMLRPC::Transport::HTTP::CGI::make_fault; -sub make_response; *make_response = \&XMLRPC::Transport::HTTP::CGI::make_response; - -1; diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm deleted file mode 100644 index fbf18cbbc..000000000 --- a/FS/FS/Conf.pm +++ /dev/null @@ -1,4235 +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::payby; -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 conf KEY [ AGENTNUM [ NODEFAULT ] ] - -Returns the L record for the key and agent. - -=cut - -sub conf { - my $self = shift; - $self->_config(@_); -} - -=item config KEY [ AGENTNUM [ NODEFAULT ] ] - -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. If NODEFAULT is true only the agent -specific value(s) is returned. - -=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,$agentonly)=@_; - 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 (!$agentonly && !$cv && defined($agentnum) && $agentnum) { - $hashref->{agentnum} = ''; - $cv = FS::Record::qsearchs('conf', $hashref); - } - return $cv; -} - -sub config { - my $self = shift; - return $self->_usecompat('config', @_) if use_confcompat; - - carp "FS::Conf->config(". join(', ', @_). ") called" - if $DEBUG > 1; - - my $cv = $self->_config(@_) 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 [ NODEFAULT ] ] - -Returns the exact scalar value for key. - -=cut - -sub config_binary { - my $self = shift; - return $self->_usecompat('config_binary', @_) if use_confcompat; - - my $cv = $self->_config(@_) or return; - length($cv->value) ? decode_base64($cv->value) : ''; -} - -=item exists KEY [ AGENTNUM [ NODEFAULT ] ] - -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)=@_; - - carp "FS::Conf->exists(". join(', ', @_). ") called" - if $DEBUG > 1; - - defined($self->_config(@_)); -} - -=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 key_orbase KEY SUFFIX - -If the config value KEY_SUFFIX exists, returns KEY_SUFFIX, otherwise returns -KEY. Useful for determining which exact configuration option is returned by -config_orbase. - -=cut - -sub key_orbase { - my $self = shift; - #no compat for this...return $self->_usecompat('config_orbase', @_) if use_confcompat; - - my( $name, $suffix ) = @_; - if ( $self->exists("${name}_$suffix") ) { - "${name}_$suffix"; - } else { - $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}++; - } - } - } - - map { $_ } #handle scalar context - 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) into -the database as a conf record (see L). 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 =~ /^(binary|image)$/ ) { - $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) 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); - - if ( $type !~ /^(binary|image)$/ ) { - - { - 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; - } - - } else { - - no warnings 'uninitialized'; - $error .= "$key fails binary comparison; " - unless scalar($self->config_binary($key)) eq scalar($compat->config_binary($key)); - - } - -#remove deprecated config on our own terms, not freeside-upgrade's -# 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. 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' => $_, - 'base_key' => $proto->key, - 'section' => $proto->section, - 'description' => 'Alternate ' . $proto->description . ' See the billing documentation 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. - -=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 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_latexcoupon -invoice_html -invoice_htmlreturnaddress -invoice_htmlfooter -invoice_htmlnotes -logo.png -logo.eps -); - -my %msg_template_options = ( - 'type' => 'select-sub', - 'options_sub' => sub { - my @templates = qsearch({ - 'table' => 'msg_template', - 'hashref' => { 'disabled' => '' }, - 'extra_sql' => ' AND '. - $FS::CurrentUser::CurrentUser->agentnums_sql(null => 1), - }); - map { $_->msgnum, $_->msgname } @templates; - }, - 'option_sub' => sub { - my $msg_template = FS::msg_template->by_key(shift); - $msg_template ? $msg_template->msgname : '' - }, - 'per_agent' => 1, -); - -my $_gateway_name = sub { - my $g = shift; - return '' if !$g; - ($g->gateway_username . '@' . $g->gateway_module); -}; - -my %payment_gateway_options = ( - 'type' => 'select-sub', - 'options_sub' => sub { - my @gateways = qsearch({ - 'table' => 'payment_gateway', - 'hashref' => { 'disabled' => '' }, - }); - map { $_->gatewaynum, $_gateway_name->($_) } @gateways; - }, - 'option_sub' => sub { - my $gateway = FS::payment_gateway->by_key(shift); - $_gateway_name->($gateway); - }, -); - -#Billing (81 items) -#Invoicing (50 items) -#UI (69 items) -#Self-service (29 items) -#... -#Unclassified (77 items) - -@config_items = map { new FS::ConfItem $_ } ( - - { - 'key' => 'address', - 'section' => 'deprecated', - 'description' => 'This configuration option is no longer used. See invoice_template instead.', - 'type' => 'text', - }, - - { - 'key' => 'alert_expiration', - 'section' => 'notification', - 'description' => 'Enable alerts about billing method expiration (i.e. expiring credit cards).', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - { - 'key' => 'alerter_template', - 'section' => 'deprecated', - 'description' => 'Template file for billing method expiration alerts (i.e. expiring credit cards).', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'alerter_msgnum', - 'section' => 'notification', - 'description' => 'Template to use for credit card expiration alerts.', - %msg_template_options, - }, - - { - 'key' => 'apacheip', - #not actually deprecated yet - #'section' => 'deprecated', - #'description' => 'DEPRECATED, add an apache export instead. Used to be the current IP address to assign to new virtual hosts', - 'section' => '', - 'description' => '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' => 'billco-url', - 'section' => 'billing', - 'description' => 'The url to use for performing uploads to the invoice mailing service.', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'billco-username', - 'section' => 'billing', - 'description' => 'The login name to use for uploads to the invoice mailing service.', - 'type' => 'text', - 'per_agent' => 1, - 'agentonly' => 1, - }, - - { - 'key' => 'billco-password', - 'section' => 'billing', - 'description' => 'The password to use for uploads to the invoice mailing service.', - 'type' => 'text', - 'per_agent' => 1, - 'agentonly' => 1, - }, - - { - 'key' => 'billco-clicode', - 'section' => 'billing', - 'description' => 'The clicode to use for uploads to the invoice mailing service.', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'business-onlinepayment', - 'section' => 'billing', - 'description' => 'Business::OnlinePayment 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 Business::OnlinePayment support for ACH transactions (defaults to regular business-onlinepayment). 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-namespace', - 'section' => 'billing', - 'description' => 'Specifies which perl module namespace (which group of collection routines) is used by default.', - 'type' => 'select', - 'select_hash' => [ - 'Business::OnlinePayment' => 'Direct API (Business::OnlinePayment)', - 'Business::OnlineThirdPartyPayment' => 'Web API (Business::ThirdPartyPayment)', - ], - }, - - { - 'key' => 'business-onlinepayment-description', - 'section' => 'billing', - 'description' => 'String passed as the description field to Business::OnlinePayment. Evaluated as a double-quoted perl string, with the following variables available: $agent (the agent name), and $pkgs (a comma-separated list of packages for which these charges apply - not available in all situations)', - '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' => 'business-onlinepayment-test_transaction', - 'section' => 'billing', - 'description' => 'Turns on the Business::OnlinePayment test_transaction flag. Note that not all gateway modules support this flag; if yours does not, transactions will still be sent live.', - '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', - '%d/%m/%Y' => 'DD/MM/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 the 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' => 'deleteinvoices', - 'section' => 'UI', - 'description' => 'Enable invoices deletions. Be very careful! Deleting an invoice will remove all traces that the invoice ever existed! Normally, you would apply a credit against the invoice instead.', #invoice voiding? - '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', - #not actually deprecated yet - #'section' => 'deprecated', - #'description' => 'DEPRECATED, 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.', - 'section' => '', - 'description' => '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' => 'DEPRECATED, now controlled by ACLs. Used to enable "unapplication" of unclosed payments.', - 'type' => 'checkbox', - }, - - { - 'key' => 'unapplycredits', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, 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:
  • 1: user -> /home/u/user
  • 2: user -> /home/u/s/user
  • -1: user -> /home/r/user
  • -2: user -> home/r/e/user
', - 'type' => 'text', - }, - - { - 'key' => 'disable_cust_attachment', - 'section' => '', - 'description' => 'Disable customer file attachments', - 'type' => 'checkbox', - }, - - { - 'key' => 'max_attachment_size', - 'section' => '', - 'description' => 'Maximum size for customer file attachments (leave blank for unlimited)', - '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' => 'invoicing', - 'description' => 'Disables postal mail invoices', - 'type' => 'checkbox', - }, - - { - 'key' => 'disablepostalinvoicedefault', - 'section' => 'invoicing', - 'description' => 'Disables postal mail invoices as the default option in the UI. Be careful not to setup customers which are not sent invoices. See emailinvoiceauto.', - 'type' => 'checkbox', - }, - - { - 'key' => 'emailinvoiceauto', - 'section' => 'invoicing', - 'description' => 'Automatically adds new accounts to the email invoice list', - 'type' => 'checkbox', - }, - - { - 'key' => 'emailinvoiceautoalways', - 'section' => 'invoicing', - 'description' => 'Automatically adds new accounts to the email invoice list even when the list contains email addresses', - 'type' => 'checkbox', - }, - - { - 'key' => 'emailinvoice-apostrophe', - 'section' => 'invoicing', - 'description' => 'Allows the apostrophe (single quote) character in the email addresses in the email invoice list.', - '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' => 'auto_router', - 'section' => '', - 'description' => 'Automatically choose the correct router/block based on supplied ip address when possible while provisioning broadband services', - 'type' => 'checkbox', - }, - - { - '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', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_subject', - 'section' => 'invoicing', - 'description' => 'Subject: header on email invoices. Defaults to "Invoice". The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_usesummary', - 'section' => 'invoicing', - 'description' => 'Indicates that html and latex invoices should be in summary style and make use of invoice_latexsummary.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice_template', - 'section' => 'invoicing', - '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 billing documentation for details.', - 'type' => 'textarea', - }, - - { - 'key' => 'invoice_html', - 'section' => 'invoicing', - 'description' => 'Optional HTML template for invoices. See the billing documentation for details.', - - 'type' => 'textarea', - }, - - { - 'key' => 'invoice_htmlnotes', - 'section' => 'invoicing', - 'description' => 'Notes section for HTML invoices. Defaults to the same data in invoice_latexnotes if not specified.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_htmlfooter', - 'section' => 'invoicing', - 'description' => 'Footer for HTML invoices. Defaults to the same data in invoice_latexfooter if not specified.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_htmlsummary', - 'section' => 'invoicing', - 'description' => 'Summary initial page for HTML invoices.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_htmlreturnaddress', - 'section' => 'invoicing', - 'description' => 'Return address for HTML invoices. Defaults to the same data in invoice_latexreturnaddress if not specified.', - 'type' => 'textarea', - }, - - { - 'key' => 'invoice_latex', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX template for typeset PostScript invoices. See the billing documentation for details.', - 'type' => 'textarea', - }, - - { - 'key' => 'invoice_latextopmargin', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice topmargin setting. Include units.', - 'type' => 'text', - 'per_agent' => 1, - 'validate' => sub { shift =~ - /^-?\d*\.?\d+(in|mm|cm|pt|em|ex|pc|bp|dd|cc|sp)$/ - ? '' : 'Invalid LaTex length'; - }, - }, - - { - 'key' => 'invoice_latexheadsep', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice headsep setting. Include units.', - 'type' => 'text', - 'per_agent' => 1, - 'validate' => sub { shift =~ - /^-?\d*\.?\d+(in|mm|cm|pt|em|ex|pc|bp|dd|cc|sp)$/ - ? '' : 'Invalid LaTex length'; - }, - }, - - { - 'key' => 'invoice_latexaddresssep', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice separation between invoice header -and customer address. Include units.', - 'type' => 'text', - 'per_agent' => 1, - 'validate' => sub { shift =~ - /^-?\d*\.?\d+(in|mm|cm|pt|em|ex|pc|bp|dd|cc|sp)$/ - ? '' : 'Invalid LaTex length'; - }, - }, - - { - 'key' => 'invoice_latextextheight', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice textheight setting. Include units.', - 'type' => 'text', - 'per_agent' => 1, - 'validate' => sub { shift =~ - /^-?\d*\.?\d+(in|mm|cm|pt|em|ex|pc|bp|dd|cc|sp)$/ - ? '' : 'Invalid LaTex length'; - }, - }, - - { - 'key' => 'invoice_latexnotes', - 'section' => 'invoicing', - 'description' => 'Notes section for LaTeX typeset PostScript invoices.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_latexfooter', - 'section' => 'invoicing', - 'description' => 'Footer for LaTeX typeset PostScript invoices.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_latexsummary', - 'section' => 'invoicing', - 'description' => 'Summary initial page for LaTeX typeset PostScript invoices.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_latexcoupon', - 'section' => 'invoicing', - 'description' => 'Remittance coupon for LaTeX typeset PostScript invoices.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_latexextracouponspace', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice textheight space to reserve for a tear off coupon. Include units.', - 'type' => 'text', - 'per_agent' => 1, - 'validate' => sub { shift =~ - /^-?\d*\.?\d+(in|mm|cm|pt|em|ex|pc|bp|dd|cc|sp)$/ - ? '' : 'Invalid LaTex length'; - }, - }, - - { - 'key' => 'invoice_latexcouponfootsep', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice separation between tear off coupon and footer. Include units.', - 'type' => 'text', - 'per_agent' => 1, - 'validate' => sub { shift =~ - /^-?\d*\.?\d+(in|mm|cm|pt|em|ex|pc|bp|dd|cc|sp)$/ - ? '' : 'Invalid LaTex length'; - }, - }, - - { - 'key' => 'invoice_latexcouponamountenclosedsep', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice separation between total due and amount enclosed line. Include units.', - 'type' => 'text', - 'per_agent' => 1, - 'validate' => sub { shift =~ - /^-?\d*\.?\d+(in|mm|cm|pt|em|ex|pc|bp|dd|cc|sp)$/ - ? '' : 'Invalid LaTex length'; - }, - }, - { - 'key' => 'invoice_latexcoupontoaddresssep', - 'section' => 'invoicing', - 'description' => 'Optional LaTeX invoice separation between invoice data and the to address (usually invoice_latexreturnaddress). Include units.', - 'type' => 'text', - 'per_agent' => 1, - 'validate' => sub { shift =~ - /^-?\d*\.?\d+(in|mm|cm|pt|em|ex|pc|bp|dd|cc|sp)$/ - ? '' : 'Invalid LaTex length'; - }, - }, - - { - 'key' => 'invoice_latexreturnaddress', - 'section' => 'invoicing', - 'description' => 'Return address for LaTeX typeset PostScript invoices.', - 'type' => 'textarea', - }, - - { - 'key' => 'invoice_latexverticalreturnaddress', - 'section' => 'invoicing', - 'description' => 'Place the return address under the company logo rather than beside it.', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_latexcouponaddcompanytoaddress', - 'section' => 'invoicing', - 'description' => 'Add the company name to the To address on the remittance coupon because the return address does not contain it.', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_latexsmallfooter', - 'section' => 'invoicing', - 'description' => 'Optional small footer for multi-page LaTeX typeset PostScript invoices.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'invoice_email_pdf', - 'section' => 'invoicing', - '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' => 'invoicing', - 'description' => 'If defined, this text will replace the default plain text invoice as the body of emailed PDF invoices.', - 'type' => 'textarea' - }, - - { - 'key' => 'invoice_print_pdf', - 'section' => 'invoicing', - 'description' => 'Store postal invoices for download in PDF format rather than printing them directly.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice_default_terms', - 'section' => 'invoicing', - '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 21', 'Net 30', 'Net 45', 'Net 60', 'Net 90' ], - }, - - { - 'key' => 'invoice_show_prior_due_date', - 'section' => 'invoicing', - 'description' => 'Show previous invoice due dates when showing prior balances. Default is to show invoice date.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice_include_aging', - 'section' => 'invoicing', - 'description' => 'Show an aging line after the prior balance section. Only valud when invoice_sections is enabled.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice_sections', - 'section' => 'invoicing', - 'description' => 'Split invoice into sections and label according to package category when enabled.', - 'type' => 'checkbox', - }, - - { - 'key' => 'usage_class_as_a_section', - 'section' => 'invoicing', - 'description' => 'Split usage into sections and label according to usage class name when enabled. Only valid when invoice_sections is enabled.', - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_phone_sections', - 'section' => 'invoicing', - 'description' => 'Create a section for each svc_phone when enabled. Only valid when invoice_sections is enabled.', - 'type' => 'checkbox', - }, - - { - 'key' => 'finance_pkgclass', - 'section' => 'billing', - 'description' => 'The default package class for late fee charges, used if the fee event does not specify a package class itself.', - 'type' => 'select-pkg_class', - }, - - { - 'key' => 'separate_usage', - 'section' => 'invoicing', - 'description' => 'Split the rated call usage into a separate line from the recurring charges.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice_send_receipts', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, this used to send an invoice copy on payments and credits. See the payment_receipt_email and XXXX instead.', - 'type' => 'checkbox', - }, - - { - 'key' => 'payment_receipt', - 'section' => 'notification', - 'description' => 'Send payment receipts.', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - { - 'key' => 'payment_receipt_msgnum', - 'section' => 'notification', - 'description' => 'Template to use for payment receipts.', - %msg_template_options, - }, - - { - 'key' => 'payment_receipt_email', - 'section' => 'deprecated', - 'description' => 'Template file for payment receipts. Payment receipts are sent to the customer email invoice destination(s) when a payment is received.', - 'type' => [qw( checkbox textarea )], - }, - - { - 'key' => 'payment_receipt-trigger', - 'section' => 'notification', - 'description' => 'When payment receipts are triggered. Defaults to when payment is made.', - 'type' => 'select', - 'select_hash' => [ - 'cust_pay' => 'When payment is made.', - 'cust_bill_pay_pkg' => 'When payment is applied.', - ], - 'per_agent' => 1, - }, - - { - 'key' => 'trigger_export_insert_on_payment', - 'section' => 'billing', - 'description' => 'Enable exports on payment application.', - 'type' => 'checkbox', - }, - - { - '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 { $_=>$_ } - #@{ FS::domain_record->rectypes } - qw(A AAAA CNAME MX NS PTR SPF SRV 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' => 'default-password-encoding', - 'section' => 'password', - 'description' => 'Default storage format for passwords', - 'type' => 'select', - 'select_hash' => [ - 'plain' => 'Plain text', - 'crypt-des' => 'Unix password (DES encrypted)', - 'crypt-md5' => 'Unix password (MD5 digest)', - 'ldap-plain' => 'LDAP (plain text)', - 'ldap-crypt' => 'LDAP (DES encrypted)', - 'ldap-md5' => 'LDAP (MD5 digest)', - 'ldap-sha1' => 'LDAP (SHA1 digest)', - 'legacy' => 'Legacy mode', - ], - }, - - { - 'key' => 'referraldefault', - 'section' => 'UI', - 'description' => 'Default referral, specified by refnum', - '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' => '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' => '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: $ip, $nasip and $nasfqdn, 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: $ip, $nasip and $nasfqdn, 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' => 'report-showpasswords', - 'section' => 'UI', - 'description' => 'This is a terrible idea. Do not enable it. STRONGLY NOT RECOMMENDED. Enables display of passwords on services reports.', - 'type' => 'checkbox', - }, - - { - 'key' => 'signupurl', - 'section' => 'UI', - 'description' => 'if you are using customer-to-customer referrals, and you enter the URL of your signup server CGI, 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' => 'smtp-username', - 'section' => '', - 'description' => 'Optional SMTP username for Freeside\'s outgoing mail', - 'type' => 'text', - }, - - { - 'key' => 'smtp-password', - 'section' => '', - 'description' => 'Optional SMTP password for Freeside\'s outgoing mail', - 'type' => 'text', - }, - - { - 'key' => 'smtp-encryption', - 'section' => '', - 'description' => 'Optional SMTP encryption method. The STARTTLS methods require smtp-username and smtp-password to be set.', - 'type' => 'select', - 'select_hash' => [ '25' => 'None (port 25)', - '25-starttls' => 'STARTTLS (port 25)', - '587-starttls' => 'STARTTLS / submission (port 587)', - '465-tls' => 'SMTPS (SSL) (port 465)', - ], - }, - - { - '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 (&) in usernames. Be careful when using this option in conjunction with exports 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', - 'per_agent' => 1, - }, - - { - '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' => 'username-colon', - 'section' => 'username', - 'description' => 'Allow the colon character (:) in usernames.', - 'type' => 'checkbox', - }, - - { - 'key' => 'username-slash', - 'section' => 'username', - 'description' => 'Allow the slash character (/) in usernames. When using, make sure to set "Home directory" to fixed and blank in all svc_acct service definitions.', - 'type' => 'checkbox', - }, - - { - 'key' => 'username-equals', - 'section' => 'username', - 'description' => 'Allow the equal sign 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' => 'self-service', - 'description' => 'Acceptable payment types for the signup server', - 'type' => 'selectmultiple', - 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY BILL COMP) ], - }, - - { - 'key' => 'selfservice-payment_gateway', - 'section' => 'self-service', - 'description' => 'Force the use of this payment gateway for self-service.', - %payment_gateway_options, - }, - - { - 'key' => 'selfservice-save_unchecked', - 'section' => 'self-service', - 'description' => 'In self-service, uncheck "Remember information" checkboxes by default (normally, they are checked by default).', - 'type' => 'checkbox', - }, - - { - 'key' => 'signup_server-default_agentnum', - 'section' => 'self-service', - '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' => 'self-service', - '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' => 'self-service', - 'description' => 'Default package for the signup server', - 'type' => 'select-part_pkg', - }, - - { - 'key' => 'signup_server-default_svcpart', - 'section' => 'self-service', - 'description' => 'Default service definition for the signup server - only necessary for services that trigger special provisioning widgets (such as DID provisioning).', - 'type' => 'select-part_svc', - }, - - { - 'key' => 'signup_server-mac_addr_svcparts', - 'section' => 'self-service', - 'description' => 'Service definitions which can receive mac addresses (current mapped to username for svc_acct).', - 'type' => 'select-part_svc', - 'multiple' => 1, - }, - - { - 'key' => 'signup_server-nomadix', - 'section' => 'self-service', - 'description' => 'Signup page Nomadix integration', - 'type' => 'checkbox', - }, - - { - 'key' => 'signup_server-service', - 'section' => 'self-service', - 'description' => 'Service for the signup server - "Account (svc_acct)" is the default setting, or "Phone number (svc_phone)" for ITSP signup', - 'type' => 'select', - 'select_hash' => [ - 'svc_acct' => 'Account (svc_acct)', - 'svc_phone' => 'Phone number (svc_phone)', - 'svc_pbx' => 'PBX (svc_pbx)', - ], - }, - - { - 'key' => 'selfservice_server-base_url', - 'section' => 'self-service', - 'description' => 'Base URL for the self-service web interface - necessary for some widgets to find their way, including retrieval of non-US state information and phone number provisioning.', - 'type' => 'text', - }, - - { - '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' => 'self-service', - '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' => 'self-service', - 'description' => 'Package Class for first optional purchase', - 'type' => 'select-pkg_class', - }, - - { - 'key' => 'signup_server-classnum3', - 'section' => 'self-service', - 'description' => 'Package Class for second optional purchase', - 'type' => 'select-pkg_class', - }, - - { - 'key' => 'selfservice-xmlrpc', - 'section' => 'self-service', - 'description' => 'Run a standalone self-service XML-RPC server on the backend (on port 8080).', - 'type' => 'checkbox', - }, - - { - 'key' => 'backend-realtime', - 'section' => 'billing', - 'description' => 'Run billing for backend signups immediately.', - 'type' => 'checkbox', - }, - - { - 'key' => 'decline_msgnum', - 'section' => 'notification', - 'description' => 'Template to use for credit card and electronic check decline messages.', - %msg_template_options, - }, - - { - 'key' => 'declinetemplate', - 'section' => 'deprecated', - 'description' => 'Template file for credit card and electronic check decline emails.', - 'type' => 'textarea', - }, - - { - 'key' => 'emaildecline', - 'section' => 'notification', - 'description' => 'Enable emailing of credit card and electronic check decline notices.', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - { - 'key' => 'emaildecline-exclude', - 'section' => 'notification', - 'description' => 'List of error messages that should not trigger email decline notices, one per line.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'cancel_msgnum', - 'section' => 'notification', - 'description' => 'Template to use for cancellation emails.', - %msg_template_options, - }, - - { - 'key' => 'cancelmessage', - 'section' => 'deprecated', - 'description' => 'Template file for cancellation emails.', - 'type' => 'textarea', - }, - - { - 'key' => 'cancelsubject', - 'section' => 'deprecated', - 'description' => 'Subject line for cancellation emails.', - 'type' => 'text', - }, - - { - 'key' => 'emailcancel', - 'section' => 'notification', - 'description' => 'Enable emailing of cancellation notices. Make sure to select the template in the cancel_msgnum option.', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - { - 'key' => 'bill_usage_on_cancel', - 'section' => 'billing', - 'description' => 'Enable automatic generation of an invoice for usage when a package is cancelled. Not all packages can do this. Usage data must already be available.', - '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' => 'enable_taxproducts', - 'section' => 'billing', - 'description' => 'Enable per-package mapping to vendor tax data from CCH or elsewhere.', - 'type' => 'checkbox', - }, - - { - 'key' => 'taxdatadirectdownload', - 'section' => 'billing', #well - 'description' => 'Enable downloading tax data directly from the vendor site. at least three lines: URL, username, and password.j', - 'type' => 'textarea', - }, - - { - 'key' => 'ignore_incalculable_taxes', - 'section' => 'billing', - 'description' => 'Prefer to invoice without tax over not billing at all', - 'type' => 'checkbox', - }, - - { - 'key' => 'welcome_msgnum', - 'section' => 'notification', - 'description' => 'Template to use for welcome messages when a svc_acct record is created.', - %msg_template_options, - }, - - { - 'key' => 'welcome_email', - 'section' => 'deprecated', - '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.', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'welcome_email-from', - 'section' => 'deprecated', - 'description' => 'From: address header for welcome email', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'welcome_email-subject', - 'section' => 'deprecated', - 'description' => 'Subject: header for welcome email', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'welcome_email-mimetype', - 'section' => 'deprecated', - '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 Text::Template documentation and the billing documentation for details on the template substitution language. A variable exists for each fieldname in the customer record ($first, $last, etc). The following additional variables are available
  • $payby - a friendler represenation of the field
  • $payinfo - the masked payment information
  • $expdate - the time at which the payment method expires (a UNIX timestamp)
  • $returnaddress - the invoice return address for this customer\'s agent
', - 'type' => 'textarea', - }, - -# { -# 'key' => 'warning_msgnum', -# 'section' => 'notification', -# 'description' => 'Template to use for warning messages, sent to the customer email invoice destination(s) when a svc_acct record has its usage drop below a threshold.', -# %msg_template_options, -# }, - - { - 'key' => 'warning_email', - 'section' => 'notification', - '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 Text::Template documentation for details on the template substitution language. The following variables are available
  • $username
  • $password
  • $first
  • $last
  • $pkg
  • $column
  • $amount
  • $threshold
', - 'type' => 'textarea', - }, - - { - 'key' => 'warning_email-from', - 'section' => 'notification', - 'description' => 'From: address header for warning email', - 'type' => 'text', - }, - - { - 'key' => 'warning_email-cc', - 'section' => 'notification', - 'description' => 'Additional recipient(s) (comma separated) for warning email when remaining usage reaches zero.', - 'type' => 'text', - }, - - { - 'key' => 'warning_email-subject', - 'section' => 'notification', - 'description' => 'Subject: header for warning email', - 'type' => 'text', - }, - - { - 'key' => 'warning_email-mimetype', - 'section' => 'notification', - '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' => 'deprecated', - 'description' => 'See batch-enable_payby and realtime-disable_payby. Used to (for CHEK): Cause per customer payment entry to be forced to a batch processor rather than performed realtime.', - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_acct-notes', - 'section' => 'deprecated', - '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', 'Cleartext-Password' ], - }, - - { - 'key' => 'radius-ip', - 'section' => '', - 'description' => 'RADIUS attribute for IP addresses.', - 'type' => 'select', - 'select_enum' => [ 'Framed-IP-Address', 'Framed-Address' ], - }, - - #http://dev.coova.org/svn/coova-chilli/doc/dictionary.chillispot - { - 'key' => 'radius-chillispot-max', - 'section' => '', - 'description' => 'Enable ChilliSpot (and CoovaChilli) Max attributes, specifically ChilliSpot-Max-{Input,Output,Total}-{Octets,Gigawords}.', - 'type' => 'checkbox', - }, - - { - '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' => 'DEPRECATED, enable the Complimentary customer access right instead. Was: Usernames (Freeside users, created with freeside-adduser) which can create complimentary customers, one per line. If no usernames are entered, all users can create complimentary accounts.', - 'type' => 'textarea', - }, - - { - 'key' => 'credit_card-recurring_billing_flag', - 'section' => 'billing', - 'description' => 'This controls when the system passes the "recurring_billing" flag on credit card transactions. If supported by your processor (and the Business::OnlinePayment processor module), passing the flag indicates this is a recurring transaction and may turn off the CVV requirement. ', - 'type' => 'select', - 'select_hash' => [ - 'actual_oncard' => 'Default/classic behavior: set the flag if a customer has actual previous charges on the card.', - 'transaction_is_recur' => 'Set the flag if the transaction itself is recurring, irregardless of previous charges on the card.', - ], - }, - - { - 'key' => 'credit_card-recurring_billing_acct_code', - 'section' => 'billing', - 'description' => 'When the "recurring billing" flag is set, also set the "acct_code" to "rebill". Useful for reporting purposes with supported gateways (PlugNPay, others?)', - 'type' => 'checkbox', - }, - - { - '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' => 'manual_process-pkgpart', - 'section' => 'billing', - 'description' => 'Package to add to each manual credit card and ACH payments entered from the backend. Enabling this option may be in violation of your merchant agreement(s), so please check them carefully before enabling this option.', - 'type' => 'select-part_pkg', - }, - - { - 'key' => 'manual_process-display', - 'section' => 'billing', - 'description' => 'When using manual_process-pkgpart, add the fee to the amount entered (default), or subtract the fee from the amount entered.', - 'type' => 'select', - 'select_hash' => [ - 'add' => 'Add fee to amount entered', - 'subtract' => 'Subtract fee from amount entered', - ], - }, - - { - 'key' => 'manual_process-skip_first', - 'section' => 'billing', - 'description' => "When using manual_process-pkgpart, omit the fee if it is the customer's first payment.", - 'type' => 'checkbox', - }, - - { - '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' => 'cust_pkg-change_pkgpart-bill_now', - 'section' => '', - 'description' => "When changing packages, bill the new package immediately. Useful for prepaid situations with RADIUS where an Expiration attribute based on the package must be present at all times.", - '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' => 'select-part_svc', - 'multiple' => 1, - }, - - { - 'key' => 'selfservice_server-primary_only', - 'section' => 'self-service', - 'description' => 'Only allow primary accounts to access self-service functionality.', - 'type' => 'checkbox', - }, - - { - 'key' => 'selfservice_server-phone_login', - 'section' => 'self-service', - 'description' => 'Allow login to self-service with phone number and PIN.', - 'type' => 'checkbox', - }, - - { - 'key' => 'selfservice_server-single_domain', - 'section' => 'self-service', - 'description' => 'If specified, only use this one domain for self-service access.', - 'type' => 'text', - }, - - { - 'key' => 'selfservice_server-login_svcpart', - 'section' => 'self-service', - 'description' => 'If specified, only allow the specified svcparts to login to self-service.', - 'type' => 'select-part_svc', - 'multiple' => 1, - }, - - { - 'key' => 'selfservice-recent-did-age', - 'section' => 'self-service', - 'description' => 'If specified, defines "recent", in number of seconds, for "Download recently allocated DIDs" in self-service.', - 'type' => 'text', - }, - - { - 'key' => 'selfservice_server-view-wholesale', - 'section' => 'self-service', - 'description' => 'If enabled, use a wholesale package view in the self-service.', - 'type' => 'checkbox', - }, - - { - 'key' => 'selfservice-agent_signup', - 'section' => 'self-service', - 'description' => 'Allow agent signup via self-service.', - 'type' => 'checkbox', - }, - - { - 'key' => 'selfservice-agent_signup-agent_type', - 'section' => 'self-service', - 'description' => 'Agent type when allowing agent signup via self-service.', - 'type' => 'select-sub', - 'options_sub' => sub { require FS::Record; - require FS::agent_type; - map { $_->typenum => $_->atype } - FS::Record::qsearch('agent_type', {} ); # disabled=>'' } ); - }, - 'option_sub' => sub { require FS::Record; - require FS::agent_type; - my $agent = FS::Record::qsearchs( - 'agent_type', { 'typenum'=>shift } - ); - $agent_type ? $agent_type->atype : ''; - }, - }, - - { - 'key' => 'selfservice-agent_login', - 'section' => 'self-service', - 'description' => 'Allow agent login via self-service.', - 'type' => 'checkbox', - }, - - { - 'key' => 'selfservice-self_suspend_reason', - 'section' => 'self-service', - 'description' => 'Suspend reason when customers suspend their own packages. Set to nothing to disallow self-suspension.', - 'type' => 'select-sub', - 'options_sub' => sub { require FS::Record; - require FS::reason; - my $type = qsearchs('reason_type', - { class => 'S' }) - or return (); - map { $_->reasonnum => $_->reason } - FS::Record::qsearch('reason', - { reason_type => $type->typenum } - ); - }, - 'option_sub' => sub { require FS::Record; - require FS::reason; - my $reason = FS::Record::qsearchs( - 'reason', { 'reasonnum' => shift } - ); - $reason ? $reason->reason : ''; - }, - - 'per_agent' => 1, - }, - - { - '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' => 'global_unique-phonenum', - 'section' => '', - 'description' => 'Global phone number uniqueness control: none (usual setting - check countrycode+phonenumun uniqueness per exports), or countrycode+phonenum (all countrycode+phonenum 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', 'countrycode+phonenum', 'disabled' ], - }, - - { - 'key' => 'global_unique-pbx_title', - 'section' => '', - 'description' => 'Global phone number uniqueness control: none (check uniqueness per exports), enabled (check across all services), or disabled (no duplicate checking).', - 'type' => 'select', - 'select_enum' => [ 'enabled', 'disabled' ], - }, - - { - 'key' => 'global_unique-pbx_id', - 'section' => '', - 'description' => 'Global PBX id uniqueness control: none (check uniqueness per exports), enabled (check across all services), or disabled (no duplicate checking).', - 'type' => 'select', - 'select_enum' => [ 'enabled', '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. RT_Internal uses the built-in RT ticketing system (see the integrated ticketing installation instructions). RT_External 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-force_default_queueid', - 'section' => '', - 'description' => 'Disallow queue selection when creating new tickets from customer view.', - 'type' => 'checkbox', - }, - { - 'key' => 'ticket_system-selfservice_queueid', - 'section' => '', - 'description' => 'Queue used when creating new customer tickets from self-service. Defautls to ticket_system-default_queueid if not specified.', - #false laziness w/above - '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, DBI:Pg:user=rt_user;password=rt_word;host=rt.example.com;dbname=rt', - 'type' => 'text', - - }, - - { - 'key' => 'ticket_system-rt_external_url', - 'section' => '', - 'description' => 'With external RT integration, the URL for the external RT installation, for example, https://rt.example.com/rt', - 'type' => 'text', - }, - - { - 'key' => 'company_name', - 'section' => 'required', - 'description' => 'Your company name', - 'type' => 'text', - 'per_agent' => 1, #XXX just FS/FS/ClientAPI/Signup.pm - }, - - { - 'key' => 'company_address', - 'section' => 'required', - 'description' => 'Your company address', - 'type' => 'textarea', - 'per_agent' => 1, - }, - - { - 'key' => 'echeck-void', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, 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' => 'DEPRECATED, 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' => 'DEPRECATED, now controlled by ACLs. Used to enable unvoiding of voided payments', - 'type' => 'checkbox', - }, - - { - 'key' => 'address1-search', - 'section' => 'UI', - 'description' => 'Enable the ability to search the address1 field from the quick customer search. Not recommended in most cases as it tends to bring up too many search results - use explicit address searching from the advanced customer search instead.', - 'type' => 'checkbox', - }, - - { - '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' => 'agent-ship_address', - 'section' => '', - 'description' => "Use the agent's master service address as the service address (only ship_address2 can be entered, if blank on the master address). Useful for multi-tenant applications.", - 'type' => 'checkbox', - }, - - { 'key' => 'referral_credit', - 'section' => 'deprecated', - 'description' => "Used to enable one-time referral credits in the amount of one month referred customer's recurring fee (irregardless of frequency). Replace with a billing event on appropriate packages.", - 'type' => 'checkbox', - }, - - { 'key' => 'selfservice_server-cache_module', - 'section' => 'self-service', - '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' => 'billing', - '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' => 'cust_bill-ftpformat', - 'section' => 'invoicing', - 'description' => 'Enable FTP of raw invoice data - format.', - 'type' => 'select', - 'select_enum' => [ '', 'default', 'billco', ], - }, - - { - 'key' => 'cust_bill-ftpserver', - 'section' => 'invoicing', - 'description' => 'Enable FTP of raw invoice data - server.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-ftpusername', - 'section' => 'invoicing', - 'description' => 'Enable FTP of raw invoice data - server.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-ftppassword', - 'section' => 'invoicing', - 'description' => 'Enable FTP of raw invoice data - server.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-ftpdir', - 'section' => 'invoicing', - 'description' => 'Enable FTP of raw invoice data - server.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-spoolformat', - 'section' => 'invoicing', - 'description' => 'Enable spooling of raw invoice data - format.', - 'type' => 'select', - 'select_enum' => [ '', 'default', 'billco', ], - }, - - { - 'key' => 'cust_bill-spoolagent', - 'section' => 'invoicing', - 'description' => 'Enable per-agent spooling of raw invoice data.', - 'type' => 'checkbox', - }, - - { - '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.', - 'type' => 'text', - }, - - { - 'key' => 'overlimit_groups', - 'section' => '', - 'description' => 'RADIUS group (or comma-separated groups) to assign to svc_acct which has exceeded its bandwidth or time limit.', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - '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' => 'cust_pkg-always_show_location', - 'section' => 'UI', - 'description' => "Always display package locations, even when they're all the default service address.", - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_pkg-group_by_location', - 'section' => 'UI', - 'description' => "Group packages by location.", - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_pkg-show_fcc_voice_grade_equivalent', - 'section' => 'UI', - 'description' => "Show a field on package definitions for assigning a DSO equivalency number suitable for use on FCC form 477.", - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_pkg-large_pkg_size', - 'section' => 'UI', - 'description' => "In customer view, summarize packages with more than this many services. Set to zero to never summarize packages.", - 'type' => 'text', - }, - - { - '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' => 'voip-cust_cdr_squelch', - 'section' => '', - 'description' => 'Enable the per-customer option for not printing CDR on invoices.', - 'type' => 'checkbox', - }, - - { - 'key' => 'voip-cdr_email', - 'section' => '', - 'description' => 'Include the call details on emailed invoices (and HTML invoices viewed in the backend), even if the customer is configured for not printing them on the invoices.', - 'type' => 'checkbox', - }, - - { - 'key' => 'voip-cust_email_csv_cdr', - 'section' => '', - 'description' => 'Enable the per-customer option for including CDR information as a CSV attachment on emailed invoices.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cgp_rule-domain_templates', - 'section' => '', - 'description' => 'Communigate Pro rule templates for domains, one per line, "svcnum Name"', - 'type' => 'textarea', - }, - - { - 'key' => 'svc_forward-no_srcsvc', - 'section' => '', - 'description' => "Don't allow forwards from existing accounts, only arbitrary addresses. Useful when exporting to systems such as Communigate Pro which treat forwards in this fashion.", - '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.', - 'type' => 'checkbox', - } -, - { - 'key' => 'tax-pkg_address', - 'section' => 'billing', - 'description' => 'By default, tax calculations are done based on the billing address. Enable this switch to calculate tax based on the package address instead (when present).', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice-ship_address', - 'section' => 'invoicing', - 'description' => 'Include the shipping address on invoices.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice-unitprice', - 'section' => 'invoicing', - 'description' => 'Enable unit pricing on invoices.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice-smallernotes', - 'section' => 'invoicing', - 'description' => 'Display the notes section in a smaller font on invoices.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice-smallerfooter', - 'section' => 'invoicing', - 'description' => 'Display footers in a smaller font on invoices.', - 'type' => 'checkbox', - }, - - { - 'key' => 'postal_invoice-fee_pkgpart', - 'section' => 'billing', - 'description' => 'This allows selection of a package to insert on invoices for customers with postal invoices selected.', - 'type' => 'select-part_pkg', - }, - - { - 'key' => 'postal_invoice-recurring_only', - 'section' => 'billing', - 'description' => 'The postal invoice fee is omitted on invoices without reucrring charges when this is set.', - 'type' => 'checkbox', - }, - - { - 'key' => 'batch-enable', - 'section' => 'deprecated', #make sure batch-enable_payby is set for - #everyone before removing - 'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.', - 'type' => 'checkbox', - }, - - { - 'key' => 'batch-enable_payby', - 'section' => 'billing', - 'description' => 'Enable batch processing for the specified payment types.', - 'type' => 'selectmultiple', - 'select_enum' => [qw( CARD CHEK )], - }, - - { - 'key' => 'realtime-disable_payby', - 'section' => 'billing', - 'description' => 'Disable realtime processing for the specified payment types.', - 'type' => 'selectmultiple', - 'select_enum' => [qw( CARD CHEK )], - }, - - { - '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', - 'paymentech', 'ach-spiritone', 'RBC' - ] - }, - - #lists could be auto-generated from pay_batch info - { - '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', 'paymentech' ] - }, - - { - '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', - 'paymentech', 'ach-spiritone', 'RBC' - ] - }, - - { - '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' => 'batchconfig-paymentech', - 'section' => 'billing', - 'description' => 'Configuration for Chase Paymentech batching, five lines: 1. BIN, 2. Terminal ID, 3. Merchant ID, 4. Username, 5. Password (for batch uploads)', - 'type' => 'textarea', - }, - - { - 'key' => 'batchconfig-RBC', - 'section' => 'billing', - 'description' => 'Configuration for Royal Bank of Canada PDS batching, four lines: 1. Client number, 2. Short name, 3. Long name, 4. Transaction code.', - 'type' => 'textarea', - }, - - { - 'key' => 'batchconfig-td_eft1464', - 'section' => 'billing', - 'description' => 'Configuration for TD Bank EFT1464 batching, five lines: 1. Originator ID, 2. Datacenter Code, 3. Short name, 4. Long name, 5. Returned payment branch number, 6. Returned payment account, 7. Transaction code.', - 'type' => 'textarea', - }, - - { - 'key' => 'batch-manual_approval', - 'section' => 'billing', - 'description' => 'Allow manual batch closure, which will approve all payments that do not yet have a status. This is not advised, but is needed for payment processors that provide a report of rejected rather than approved payments.', - 'type' => 'checkbox', - }, - - { - 'key' => 'payment_history-years', - 'section' => 'UI', - 'description' => 'Number of years of payment history to show by default. Currently defaults to 2.', - 'type' => 'text', - }, - - { - 'key' => 'change_history-years', - 'section' => 'UI', - 'description' => 'Number of years of change history to show by default. Currently defaults to 0.5.', - 'type' => 'text', - }, - - { - 'key' => 'cust_main-packages-years', - 'section' => 'UI', - 'description' => 'Number of years to show old (cancelled and one-time charge) packages 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-install_welcome', - 'section' => 'UI', - 'description' => 'New install welcome screen.', - 'type' => 'select', - 'select_enum' => [ '', 'ITSP_fsinc_hosted', ], - }, - - { - 'key' => 'dashboard-toplist', - 'section' => 'UI', - 'description' => 'List of items to display on the top of the front page', - 'type' => 'textarea', - }, - - { - 'key' => 'impending_recur_msgnum', - 'section' => 'notification', - 'description' => 'Template to use for alerts about first-time recurring billing.', - %msg_template_options, - }, - - { - 'key' => 'impending_recur_template', - 'section' => 'deprecated', - 'description' => 'Template file for alerts about looming first time recurrant billing. See the Text::Template documentation for details on the template substitition language. Also see packages with a flat price plan The following variables are available
  • $packages allowing $packages->[0] thru $packages->[n]
  • $package the first package, same as $packages->[0]
  • $recurdates allowing $recurdates->[0] thru $recurdates->[n]
  • $recurdate the first recurdate, same as $recurdate->[0]
  • $first
  • $last
', -#
  • $payby
  • $expdate most likely only confuse - 'type' => 'textarea', - }, - - { - 'key' => 'logo.png', - 'section' => 'UI', #'invoicing' ? - 'description' => 'Company logo for HTML invoices and the backoffice interface, in PNG format. Suggested size somewhere near 92x62.', - 'type' => 'image', - 'per_agent' => 1, #XXX just view/logo.cgi, which is for the global - #old-style editor anyway...? - }, - - { - 'key' => 'logo.eps', - 'section' => 'invoicing', - 'description' => 'Company logo for printed and PDF invoices, in EPS format.', - 'type' => 'image', - 'per_agent' => 1, #XXX as above, kinda - }, - - { - 'key' => 'selfservice-ignore_quantity', - 'section' => 'self-service', - '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' => 'self-service', - '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' => 'select-part_pkg', - 'multiple' => 1, - }, - - { - 'key' => 'cust_main-require_phone', - 'section' => '', - 'description' => 'Require daytime or night phone 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' => 'deprecated', - 'description' => 'Used to be the group to use for new, automatically generated credit reasons resulting from referrals. Now set in a package billing event for the referral.', - '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', #self-service? - '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' => 'prepayment_discounts-credit_type', - 'section' => 'billing', - 'description' => 'Enables the offering of prepayment discounts and establishes the credit reason type.', - '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. Note: You should manually remove stored paymasks if you change this value on an existing database, to avoid problems using stored cards.', - 'type' => 'select', - 'select_hash' => [ - '' => '123456xxxxxx1234', - 'first6last2' => '123456xxxxxxxx12', - 'first4last4' => '1234xxxxxxxx1234', - 'first4last2' => '1234xxxxxxxxxx12', - 'first2last4' => '12xxxxxxxxxx1234', - 'first2last2' => '12xxxxxxxxxxxx12', - 'first0last4' => 'xxxxxxxxxxxx1234', - 'first0last2' => 'xxxxxxxxxxxxxx12', - ], - }, - - { - 'key' => 'disable_previous_balance', - 'section' => 'invoicing', - 'description' => 'Disable inclusion of previous balance, payment, and credit lines on invoices', - 'type' => 'checkbox', - }, - - { - 'key' => 'previous_balance-exclude_from_total', - 'section' => 'invoicing', - 'description' => 'Do not include previous balance in the \'Total\' line. Only meaningful when invoice_sections is false. Optionally provide text to override the Total New Charges description', - 'type' => [ qw(checkbox text) ], - }, - - { - 'key' => 'previous_balance-summary_only', - 'section' => 'invoicing', - 'description' => 'Only show a single line summarizing the total previous balance rather than one line per invoice.', - 'type' => 'checkbox', - }, - - { - 'key' => 'balance_due_below_line', - 'section' => 'invoicing', - 'description' => 'Place the balance due message below a line. Only meaningful when when invoice_sections is false.', - 'type' => 'checkbox', - }, - - { - 'key' => 'usps_webtools-userid', - 'section' => 'UI', - 'description' => 'Production UserID for USPS web tools. Enables USPS address standardization. See the USPS website, register and agree not to use the tools for batch purposes.', - 'type' => 'text', - }, - - { - 'key' => 'usps_webtools-password', - 'section' => 'UI', - 'description' => 'Production password for USPS web tools. Enables USPS address standardization. See USPS website, register and agree not to use the tools for batch purposes.', - 'type' => 'text', - }, - - { - 'key' => 'cust_main-auto_standardize_address', - 'section' => 'UI', - 'description' => 'When using USPS web tools, automatically standardize the address without asking.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-require_censustract', - 'section' => 'UI', - 'description' => 'Customer is required to have a census tract. Useful for FCC form 477 reports. See also: cust_main-auto_standardize_address', - 'type' => 'checkbox', - }, - - { - 'key' => 'census_year', - 'section' => 'UI', - 'description' => 'The year to use in census tract lookups', - 'type' => 'select', - 'select_enum' => [ qw( 2010 2009 2008 ) ], - }, - - { - 'key' => 'company_latitude', - 'section' => 'UI', - 'description' => 'Your company latitude (-90 through 90)', - 'type' => 'text', - }, - - { - 'key' => 'company_longitude', - 'section' => 'UI', - 'description' => 'Your company longitude (-180 thru 180)', - 'type' => 'text', - }, - - { - 'key' => 'disable_acl_changes', - 'section' => '', - 'description' => 'Disable all ACL changes, for demos.', - 'type' => 'checkbox', - }, - - { - 'key' => 'disable_settings_changes', - 'section' => '', - 'description' => 'Disable all settings changes, for demos, except for the usernames given in the comma-separated list.', - 'type' => [qw( checkbox text )], - }, - - { - 'key' => 'cust_main-edit_agent_custid', - 'section' => 'UI', - 'description' => 'Enable editing of the agent_custid field.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-default_agent_custid', - 'section' => 'UI', - 'description' => 'Display the agent_custid field when available instead of the custnum field.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-title-display_custnum', - 'section' => 'UI', - 'description' => 'Add the display_custom (agent_custid or custnum) to the title on customer view pages.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_bill-default_agent_invid', - 'section' => 'UI', - 'description' => 'Display the agent_invid field when available instead of the invnum field.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-auto_agent_custid', - 'section' => 'UI', - 'description' => 'Automatically assign an agent_custid - select format', - 'type' => 'select', - 'select_hash' => [ '' => 'No', - '1YMMXXXXXXXX' => '1YMMXXXXXXXX', - ], - }, - - { - 'key' => 'cust_main-default_areacode', - 'section' => 'UI', - 'description' => 'Default area code for customers.', - 'type' => 'text', - }, - - { - 'key' => 'order_pkg-no_start_date', - 'section' => 'UI', - 'description' => 'Don\'t set a default start date for new packages.', - 'type' => 'checkbox', - }, - - { - 'key' => 'mcp_svcpart', - 'section' => '', - 'description' => 'Master Control Program svcpart. Leave this blank.', - 'type' => 'text', #select-part_svc - }, - - { - 'key' => 'cust_bill-max_same_services', - 'section' => 'invoicing', - 'description' => 'Maximum number of the same service to list individually on invoices before condensing to a single line listing the number of services. Defaults to 5.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-consolidate_services', - 'section' => 'invoicing', - 'description' => 'Consolidate service display into fewer lines on invoices rather than one per service.', - 'type' => 'checkbox', - }, - - { - 'key' => 'suspend_email_admin', - 'section' => '', - 'description' => 'Destination admin email address to enable suspension notices', - 'type' => 'text', - }, - - { - 'key' => 'email_report-subject', - 'section' => '', - 'description' => 'Subject for reports emailed by freeside-fetch. Defaults to "Freeside report".', - 'type' => 'text', - }, - - { - 'key' => 'selfservice-head', - 'section' => 'self-service', - 'description' => 'HTML for the HEAD section of the self-service interface, typically used for LINK stylesheet tags', - 'type' => 'textarea', #htmlarea? - 'per_agent' => 1, - }, - - - { - 'key' => 'selfservice-body_header', - 'section' => 'self-service', - 'description' => 'HTML header for the self-service interface', - 'type' => 'textarea', #htmlarea? - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-body_footer', - 'section' => 'self-service', - 'description' => 'HTML footer for the self-service interface', - 'type' => 'textarea', #htmlarea? - 'per_agent' => 1, - }, - - - { - 'key' => 'selfservice-body_bgcolor', - 'section' => 'self-service', - 'description' => 'HTML background color for the self-service interface, for example, #FFFFFF', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-box_bgcolor', - 'section' => 'self-service', - 'description' => 'HTML color for self-service interface input boxes, for example, #C0C0C0', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-text_color', - 'section' => 'self-service', - 'description' => 'HTML text color for the self-service interface, for example, #000000', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-link_color', - 'section' => 'self-service', - 'description' => 'HTML link color for the self-service interface, for example, #0000FF', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-vlink_color', - 'section' => 'self-service', - 'description' => 'HTML visited link color for the self-service interface, for example, #FF00FF', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-hlink_color', - 'section' => 'self-service', - 'description' => 'HTML hover link color for the self-service interface, for example, #808080', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-alink_color', - 'section' => 'self-service', - 'description' => 'HTML active (clicked) link color for the self-service interface, for example, #808080', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-font', - 'section' => 'self-service', - 'description' => 'HTML font CSS for the self-service interface, for example, 0.9em/1.5em Arial, Helvetica, Geneva, sans-serif', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-title_color', - 'section' => 'self-service', - 'description' => 'HTML color for the self-service title, for example, #000000', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-title_align', - 'section' => 'self-service', - 'description' => 'HTML alignment for the self-service title, for example, center', - 'type' => 'text', - 'per_agent' => 1, - }, - { - 'key' => 'selfservice-title_size', - 'section' => 'self-service', - 'description' => 'HTML font size for the self-service title, for example, 3', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-title_left_image', - 'section' => 'self-service', - 'description' => 'Image used for the top of the menu in the self-service interface, in PNG format.', - 'type' => 'image', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-title_right_image', - 'section' => 'self-service', - 'description' => 'Image used for the top of the menu in the self-service interface, in PNG format.', - 'type' => 'image', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-menu_skipblanks', - 'section' => 'self-service', - 'description' => 'Skip blank (spacer) entries in the self-service menu', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-menu_skipheadings', - 'section' => 'self-service', - 'description' => 'Skip the unclickable heading entries in the self-service menu', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-menu_bgcolor', - 'section' => 'self-service', - 'description' => 'HTML color for the self-service menu, for example, #C0C0C0', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-menu_fontsize', - 'section' => 'self-service', - 'description' => 'HTML font size for the self-service menu, for example, -1', - 'type' => 'text', - 'per_agent' => 1, - }, - { - 'key' => 'selfservice-menu_nounderline', - 'section' => 'self-service', - 'description' => 'Styles menu links in the self-service without underlining.', - 'type' => 'checkbox', - 'per_agent' => 1, - }, - - - { - 'key' => 'selfservice-menu_top_image', - 'section' => 'self-service', - 'description' => 'Image used for the top of the menu in the self-service interface, in PNG format.', - 'type' => 'image', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-menu_body_image', - 'section' => 'self-service', - 'description' => 'Repeating image used for the body of the menu in the self-service interface, in PNG format.', - 'type' => 'image', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-menu_bottom_image', - 'section' => 'self-service', - 'description' => 'Image used for the bottom of the menu in the self-service interface, in PNG format.', - 'type' => 'image', - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-bulk_format', - 'section' => 'deprecated', - 'description' => 'Parameter arrangement for selfservice bulk features', - 'type' => 'select', - 'select_enum' => [ '', 'izoom-soap', 'izoom-ftp' ], - 'per_agent' => 1, - }, - - { - 'key' => 'selfservice-bulk_ftp_dir', - 'section' => 'deprecated', - 'description' => 'Enable bulk ftp provisioning in this folder', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'signup-no_company', - 'section' => 'self-service', - 'description' => "Don't display a field for company name on signup.", - 'type' => 'checkbox', - }, - - { - 'key' => 'signup-recommend_email', - 'section' => 'self-service', - 'description' => 'Encourage the entry of an invoicing email address on signup.', - 'type' => 'checkbox', - }, - - { - 'key' => 'signup-recommend_daytime', - 'section' => 'self-service', - 'description' => 'Encourage the entry of a daytime phone number invoicing email address on signup.', - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_phone-radius-default_password', - 'section' => '', - 'description' => 'Default password when exporting svc_phone records to RADIUS', - 'type' => 'text', - }, - - { - 'key' => 'svc_phone-allow_alpha_phonenum', - 'section' => '', - 'description' => 'Allow letters in phone numbers.', - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_phone-domain', - 'section' => '', - 'description' => 'Track an optional domain association with each phone service.', - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_phone-phone_name-max_length', - 'section' => '', - 'description' => 'Maximum length of the phone service "Name" field (svc_phone.phone_name). Sometimes useful to limit this (to 15?) when exporting as Caller ID data.', - 'type' => 'text', - }, - - { - 'key' => 'svc_phone-lnp', - 'section' => '', - 'description' => 'Enables Number Portability features for svc_phone', - 'type' => 'checkbox', - }, - - { - 'key' => 'default_phone_countrycode', - 'section' => '', - 'description' => 'Default countrcode', - 'type' => 'text', - }, - - { - 'key' => 'cdr-charged_party-field', - 'section' => '', - 'description' => 'Set the charged_party field of CDRs to this field.', - 'type' => 'select-sub', - 'options_sub' => sub { my $fields = FS::cdr->table_info->{'fields'}; - map { $_ => $fields->{$_}||$_ } - grep { $_ !~ /^(acctid|charged_party)$/ } - FS::Schema::dbdef->table('cdr')->columns; - }, - 'option_sub' => sub { my $f = shift; - FS::cdr->table_info->{'fields'}{$f} || $f; - }, - }, - - #probably deprecate in favor of cdr-charged_party-field above - { - 'key' => 'cdr-charged_party-accountcode', - 'section' => '', - 'description' => 'Set the charged_party field of CDRs to the accountcode.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cdr-charged_party-accountcode-trim_leading_0s', - 'section' => '', - 'description' => 'When setting the charged_party field of CDRs to the accountcode, trim any leading zeros.', - 'type' => 'checkbox', - }, - -# { -# 'key' => 'cdr-charged_party-truncate_prefix', -# 'section' => '', -# 'description' => 'If the charged_party field has this prefix, truncate it to the length in cdr-charged_party-truncate_length.', -# 'type' => 'text', -# }, -# -# { -# 'key' => 'cdr-charged_party-truncate_length', -# 'section' => '', -# 'description' => 'If the charged_party field has the prefix in cdr-charged_party-truncate_prefix, truncate it to this length.', -# 'type' => 'text', -# }, - - { - 'key' => 'cdr-charged_party_rewrite', - 'section' => '', - 'description' => 'Do charged party rewriting in the freeside-cdrrewrited daemon; useful if CDRs are being dropped off directly in the database and require special charged_party processing such as cdr-charged_party-accountcode or cdr-charged_party-truncate*.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cdr-taqua-da_rewrite', - 'section' => '', - 'description' => 'For the Taqua CDR format, a comma-separated list of directory assistance 800 numbers. Any CDRs with these numbers as "BilledNumber" will be rewritten to the "CallingPartyNumber" (and CallType "12") on import.', - 'type' => 'text', - }, - - { - 'key' => 'cust_pkg-show_autosuspend', - 'section' => 'UI', - 'description' => 'Show package auto-suspend dates. Use with caution for now; can slow down customer view for large insallations.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cdr-asterisk_forward_rewrite', - 'section' => '', - 'description' => 'Enable special processing for CDRs representing forwarded calls: For CDRs that have a dcontext that starts with "Local/" but does not match dst, set charged_party to dst, parse a new dst from dstchannel, and set amaflags to "2" ("BILL"/"BILLING").', - 'type' => 'checkbox', - }, - - { - 'key' => 'sg-multicustomer_hack', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'checkbox', - }, - - { - 'key' => 'sg-ping_username', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'text', - }, - - { - 'key' => 'sg-ping_password', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'text', - }, - - { - 'key' => 'sg-login_username', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'text', - }, - - { - 'key' => 'mc-outbound_packages', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'select-part_pkg', - 'multiple' => 1, - }, - - { - 'key' => 'disable-cust-pkg_class', - 'section' => 'UI', - 'description' => 'Disable the two-step dropdown for selecting package class and package, and return to the classic single dropdown.', - 'type' => 'checkbox', - }, - - { - 'key' => 'queued-max_kids', - 'section' => '', - 'description' => 'Maximum number of queued processes. Defaults to 10.', - 'type' => 'text', - }, - - { - 'key' => 'queued-sleep_time', - 'section' => '', - 'description' => 'Time to sleep between attempts to find new jobs to process in the queue. Defaults to 10. Installations doing real-time CDR processing for prepaid may want to set it lower.', - 'type' => 'text', - }, - - { - 'key' => 'cancelled_cust-noevents', - 'section' => 'billing', - 'description' => "Don't run events for cancelled customers", - 'type' => 'checkbox', - }, - - { - 'key' => 'agent-invoice_template', - 'section' => 'invoicing', - 'description' => 'Enable display/edit of old-style per-agent invoice template selection', - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_broadband-manage_link', - 'section' => 'UI', - 'description' => 'URL for svc_broadband "Manage Device" link. The following substitutions are available: $ip_addr.', - 'type' => 'text', - }, - - #more fine-grained, service def-level control could be useful eventually? - { - 'key' => 'svc_broadband-allow_null_ip_addr', - 'section' => '', - 'description' => '', - 'type' => 'checkbox', - }, - - { - 'key' => 'tax-report_groups', - 'section' => '', - 'description' => 'List of grouping possibilities for tax names on reports, one per line, "label op value" (op can be = or !=).', - 'type' => 'textarea', - }, - - { - 'key' => 'tax-cust_exempt-groups', - 'section' => '', - 'description' => 'List of grouping possibilities for tax names, for per-customer exemption purposes, one tax name per line. For example, "GST" would indicate the ability to exempt customers individually from taxes named "GST" (but not other taxes).', - 'type' => 'textarea', - }, - - { - 'key' => 'cust_main-default_view', - 'section' => 'UI', - 'description' => 'Default customer view, for users who have not selected a default view in their preferences.', - 'type' => 'select', - 'select_hash' => [ - #false laziness w/view/cust_main.cgi and pref/pref.html - 'basics' => 'Basics', - 'notes' => 'Notes', - 'tickets' => 'Tickets', - 'packages' => 'Packages', - 'payment_history' => 'Payment History', - 'change_history' => 'Change History', - 'jumbo' => 'Jumbo', - ], - }, - - { - 'key' => 'enable_tax_adjustments', - 'section' => 'billing', - 'description' => 'Enable the ability to add manual tax adjustments.', - 'type' => 'checkbox', - }, - - { - 'key' => 'rt-crontool', - 'section' => '', - 'description' => 'Enable the RT CronTool extension.', - 'type' => 'checkbox', - }, - - { - 'key' => 'pkg-balances', - 'section' => 'billing', - 'description' => 'Enable experimental package balances. Not recommended for general use.', - 'type' => 'checkbox', - }, - - { - 'key' => 'pkg-addon_classnum', - 'section' => 'billing', - 'description' => 'Enable the ability to restrict additional package orders based on package class.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-edit_signupdate', - 'section' => 'UI', - 'descritpion' => 'Enable manual editing of the signup date.', - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_acct-disable_access_number', - 'section' => 'UI', - 'descritpion' => 'Disable access number selection.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_bill_pay_pkg-manual', - 'section' => 'UI', - 'description' => 'Allow manual application of payments to line items.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_credit_bill_pkg-manual', - 'section' => 'UI', - 'description' => 'Allow manual application of credits to line items.', - 'type' => 'checkbox', - }, - - { - 'key' => 'breakage-days', - 'section' => 'billing', - 'description' => 'If set to a number of days, after an account goes that long without activity, recognizes any outstanding payments and credits as "breakage" by creating a breakage charge and invoice.', - 'type' => 'text', - 'per_agent' => 1, - }, - - { - 'key' => 'breakage-pkg_class', - 'section' => 'billing', - 'description' => 'Package class to use for breakage reconciliation.', - 'type' => 'select-pkg_class', - }, - - { - 'key' => 'disable_cron_billing', - 'section' => 'billing', - 'description' => 'Disable billing and collection from being run by freeside-daily and freeside-monthly, while still allowing other actions to run, such as notifications and backup.', - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_domain-edit_domain', - 'section' => '', - 'description' => 'Enable domain renaming', - 'type' => 'checkbox', - }, - - { - 'key' => 'enable_legacy_prepaid_income', - 'section' => '', - 'description' => "Enable legacy prepaid income reporting. Only useful when you have imported pre-Freeside packages with longer-than-monthly duration, and need to do prepaid income reporting on them before they've been invoiced the first time.", - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-exports', - 'section' => '', - 'description' => 'Export(s) to call on cust_main insert, modification and deletion.', - 'type' => 'select-sub', - 'multiple' => 1, - 'options_sub' => sub { - require FS::Record; - require FS::part_export; - my @part_export = - map { qsearch( 'part_export', {exporttype => $_ } ) } - keys %{FS::part_export::export_info('cust_main')}; - map { $_->exportnum => $_->exporttype.' to '.$_->machine } @part_export; - }, - 'option_sub' => sub { - require FS::Record; - require FS::part_export; - my $part_export = FS::Record::qsearchs( - 'part_export', { 'exportnum' => shift } - ); - $part_export - ? $part_export->exporttype.' to '.$part_export->machine - : ''; - }, - }, - - { - 'key' => 'cust_tag-location', - 'section' => 'UI', - 'description' => 'Location where customer tags are displayed.', - 'type' => 'select', - 'select_enum' => [ 'misc_info', 'top' ], - }, - - { - 'key' => 'maestro-status_test', - 'section' => 'UI', - 'description' => 'Display a link to the maestro status test page on the customer view page', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-custom_link', - 'section' => 'UI', - 'description' => 'URL to use as source for the "Custom" tab in the View Customer page. The custnum will be appended.', - 'type' => 'text', - }, - - { - 'key' => 'cust_main-custom_title', - 'section' => 'UI', - 'description' => 'Title for the "Custom" tab in the View Customer page.', - 'type' => 'text', - }, - - { - 'key' => 'part_pkg-default_suspend_bill', - 'section' => 'billing', - 'description' => 'Default the "Continue recurring billing while suspended" flag to on for new package definitions.', - 'type' => 'checkbox', - }, - - { - 'key' => 'qual-alt-address-format', - 'section' => 'UI', - 'description' => 'Enable the alternate address format (location type, number, and kind) on qualifications', - 'type' => 'checkbox', - }, - - { - 'key' => 'note-classes', - 'section' => 'UI', - 'description' => 'Use customer note classes', - 'type' => 'select', - 'select_hash' => [ - 0 => 'Disabled', - 1 => 'Enabled', - 2 => 'Enabled, with tabs', - ], - }, - - { - 'key' => 'svc_acct-cf_privatekey-message', - 'section' => '', - 'description' => 'For internal use: HTML displayed when cf_privatekey field is set.', - 'type' => 'textarea', - }, - - { - 'key' => 'menu-prepend_links', - 'section' => 'UI', - 'description' => 'Links to prepend to the main menu, one per line, with format "URL Link Label (optional ALT popup)".', - 'type' => 'textarea', - }, - - { - 'key' => 'cust_main-external_links', - 'section' => 'UI', - 'description' => 'External links available in customer view, one per line, with format "URL Link Label (optional ALT popup)". The URL will have custnum appended.', - 'type' => 'textarea', - }, - - { key => "apacheroot", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "apachemachine", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "apachemachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "bindprimary", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "bindsecondaries", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "bsdshellmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "cyrus", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "cp_app", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "erpcdmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "icradiusmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "icradius_mysqldest", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "icradius_mysqlsource", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "icradius_secrets", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "maildisablecatchall", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "mxmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "nsmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "arecords", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "cnamerecords", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "nismachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "qmailmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "radiusmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "sendmailconfigpath", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "sendmailmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "sendmailrestart", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "shellmachine", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "shellmachine-useradd", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "shellmachine-userdel", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "shellmachine-usermod", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "shellmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "radiusprepend", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "textradiusprepend", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "username_policy", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "vpopmailmachines", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "vpopmailrestart", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "safe-part_pkg", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "selfservice_server-quiet", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "signup_server-quiet", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "signup_server-email", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "vonage-username", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "vonage-password", section => "deprecated", description => "DEPRECATED", type => "text" }, - { key => "vonage-fromnumber", section => "deprecated", description => "DEPRECATED", type => "text" }, - -); - -1; - diff --git a/FS/FS/ConfDefaults.pm b/FS/FS/ConfDefaults.pm deleted file mode 100644 index de65b44a9..000000000 --- a/FS/FS/ConfDefaults.pm +++ /dev/null @@ -1,86 +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 contact if present)', - 'Cust# | Cust. Status | (bill) Name | (bill) Company | (service) Name | (service) Company' => - 'custnum | Status | Last, First | Company | (same for service contact 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 | (address) | 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 | (address) | (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 | (address) | (all phones) | Invoicing email(s) | Payment Type | Current Balance', - - 'Cust# | Cust. Status | (bill) Name | (bill) Company | (bill) Address 1 | (bill) Address 2 | (bill) City | (bill) State | (bill) Zip | (bill) Country | (bill) Day phone | (bill) Night phone | (service) Name | (service) Company | (service) Address 1 | (service) Address 2 | (service) City | (service) State | (service) Zip | (service) Country | (service) Day phone | (service) Night phone | Invoicing email(s)' => - 'custnum | Status | Last, First | Company | (address) | Day phone | Night phone | (service address) | Invoicing email(s)', - - 'Cust# | Cust. Status | (bill) Name | (bill) Company | (bill) Address 1 | (bill) Address 2 | (bill) City | (bill) State | (bill) Zip | (bill) Country | (bill) Day phone | (bill) Night phone | (bill) Fax number | (service) Name | (service) Company | (service) Address 1 | (service) Address 2 | (service) City | (service) State | (service) Zip | (service) Country | (service) Day phone | (service) Night phone | (service) Fax number | Invoicing email(s) | Payment Type' => - 'custnum | Status | Last, First | Company | (address) | (all phones) | (service address) | Invoicing email(s) | Payment Type', - - 'Cust# | Cust. Status | (bill) Name | (bill) Company | (bill) Address 1 | (bill) Address 2 | (bill) City | (bill) State | (bill) Zip | (bill) Country | (bill) Day phone | (bill) Night phone | (bill) Fax number | (service) Name | (service) Company | (service) Address 1 | (service) Address 2 | (service) City | (service) State | (service) Zip | (service) Country | (service) Day phone | (service) Night phone | (service) Fax number | Invoicing email(s) | Payment Type | Current Balance' => - 'custnum | Status | Last, First | Company | (address) | (all phones) | (service address) | Invoicing email(s) | Payment Type | Current Balance', - - 'Invoicing email(s)' => 'Invoicing email(s)', - 'Cust# | Invoicing email(s)' => 'custnum | Invoicing email(s)', - -); } - -=back - -=head1 BUGS - -Not yet. - -=head1 SEE ALSO - -L - -=cut - -1; diff --git a/FS/FS/ConfItem.pm b/FS/FS/ConfItem.pm deleted file mode 100644 index a0e997ac7..000000000 --- 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 - -=cut - -1; - diff --git a/FS/FS/Conf_compat17.pm b/FS/FS/Conf_compat17.pm deleted file mode 100644 index 15d4738f5..000000000 --- a/FS/FS/Conf_compat17.pm +++ /dev/null @@ -1,2520 +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. - -=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 billing documentation 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 billing documentation 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 billing documentation 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 billing documentation 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 invoice_template instead.', - 'type' => 'text', - }, - - { - 'key' => 'alerter_template', - 'section' => 'billing', - 'description' => 'Template file for billing method expiration alerts. See the billing documentation for details.', - 'type' => 'textarea', - }, - - { - 'key' => 'apacheroot', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a www_shellcommands export instead. The directory containing Apache virtual hosts', - 'type' => 'text', - }, - - { - 'key' => 'apacheip', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add an apache export instead. Used to be the current IP address to assign to new virtual hosts', - 'type' => 'text', - }, - - { - 'key' => 'apachemachine', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a www_shellcommands export 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' => 'DEPRECATED, add an apache export 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 Include directive.', - 'type' => 'textarea', - }, - - { - 'key' => 'bindprimary', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a bind export 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' => 'DEPRECATED, add a bind_slave export 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' => 'Business::OnlinePayment 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 Business::OnlinePayment support for ACH transactions (defaults to regular business-onlinepayment). 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 Business::OnlinePayment. Evaluated as a double-quoted perl string, with the following variables available: $agent (the agent name), and $pkgs (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' => 'DEPRECATED, add a bsdshell export instead. Your BSD flavored shell (and mail) machines, one per line. This enables export of `/etc/passwd\' and `/etc/master.passwd\'.', - 'type' => 'textarea', - }, - - { - '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' => 'cyrus', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a cyrus export instead. This option used to integrate with Cyrus IMAP Server, 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' => 'DEPRECATED, add a cp export instead. This option used to integrate with Critial Path Account Provisioning Protocol, 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 the 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' => 'deleteinvoices', - 'section' => 'UI', - 'description' => 'Enable invoices deletions. Be very careful! Deleting an invoice will remove all traces that the invoice ever existed! Normally, you would apply a credit against the invoice instead.', #invoice voiding? - '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' => 'DEPRECATED, 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' => 'DEPRECATED, now controlled by ACLs. Used to enable "unapplication" of unclosed payments.', - 'type' => 'checkbox', - }, - - { - 'key' => 'unapplycredits', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, 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:
    • 1: user -> /home/u/user
    • 2: user -> /home/u/s/user
    • -1: user -> /home/r/user
    • -2: user -> home/r/e/user
    ', - '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 emailinvoiceauto.', - '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' => 'DEPRECATED, 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' => 'DEPRECATED, add an sqlradius export instead. This option used to enable radcheck and radreply table population - by default in the Freeside database, or in the database specified by the icradius_secrets 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.
    ADDITIONAL DEPRECATED FUNCTIONALITY (instead use MySQL replication or point icradius_secrets to the external database) - your ICRADIUS machines or FreeRADIUS (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: "radius.isp.tld radius_db radius_user passw0rd"
    ', - 'type' => [qw( checkbox textarea )], - }, - - { - 'key' => 'icradius_mysqldest', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add an sqlradius export 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' => 'DEPRECATED, add an sqlradius export 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' => 'DEPRECATED, add an sqlradius export 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_subject', - 'section' => 'billing', - 'description' => 'Subject: header on email invoices. Defaults to "Invoice". The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.', - 'type' => 'text', - }, - - { - 'key' => 'invoice_template', - 'section' => 'required', - 'description' => 'Required template file for invoices. See the billing documentation for details.', - 'type' => 'textarea', - }, - - { - 'key' => 'invoice_html', - 'section' => 'billing', - 'description' => 'Optional HTML template for invoices. See the billing documentation 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 billing documentation 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_latexcoupon', - 'section' => 'billing', - 'description' => 'Remittance coupon 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' => 'DEPRECATED, 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 Text::Template documentation for details on the template substitution language. The following variables are available:
    • $date
    • $name
    • $paynum - Freeside payment number
    • $paid - Amount of payment
    • $payby - Payment type (Card, Check, Electronic check, etc.)
    • $payinfo - Masked credit card number or check number
    • $balance - New balance
    ', - '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' => 'DEPRECATED, 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' => 'DEPRECATED. 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' => 'DEPRECATED, add qmail and shellcommands exports 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 shellmachine option.', - 'type' => [qw( checkbox textarea )], - }, - - { - 'key' => 'radiusmachines', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add an sqlradius export 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' => 'DEPRECATED, add a sendmail export instead. Used to be sendmail configuration file path. Defaults to `/etc\'. Many newer distributions use `/etc/mail\'.', - 'type' => 'text', - }, - - { - 'key' => 'sendmailmachines', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a sendmail export 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' => 'DEPRECATED, add a sendmail export 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: $ip, $nasip and $nasfqdn, 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: $ip, $nasip and $nasfqdn, 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' => 'DEPRECATED, add a shellcommands export 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' => 'DEPRECATED, add a shellcommands export instead. This option used to contain command(s) to run on shellmachine when an account is created. If the shellmachine option is set but this option is not, useradd -d $dir -m -s $shell -u $uid $username is the default. If this option is set but empty, cp -pr /etc/skel $dir; chown -R $uid.$gid $dir is the default instead. Otherwise the value is evaluated as a double-quoted perl string, with the following variables available: $username, $uid, $gid, $dir, and $shell.', - 'type' => [qw( checkbox text )], - }, - - { - 'key' => 'shellmachine-userdel', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a shellcommands export instead. This option used to contain command(s) to run on shellmachine when an account is deleted. If the shellmachine option is set but this option is not, userdel $username is the default. If this option is set but empty, rm -rf $dir is the default instead. Otherwise the value is evaluated as a double-quoted perl string, with the following variables available: $username and $dir.', - 'type' => [qw( checkbox text )], - }, - - { - 'key' => 'shellmachine-usermod', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a shellcommands export instead. This option used to contain command(s) to run on shellmachine when an account is modified. If the shellmachine option is set but this option is empty, [ -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 ) is the default. Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: $old_dir, $new_dir, $uid and $gid.', - #'type' => [qw( checkbox text )], - 'type' => 'text', - }, - - { - 'key' => 'shellmachines', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a sysvshell export 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 signup server CGI, 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' => 'DEPRECATED, 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' => 'DEPRECATED, 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 (&) in usernames. Be careful when using this option in conjunction with exports 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' => 'DEPRECATED, add a vpopmail export 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: poptoaster.domain.tld /home/vpopmail 508 508 Note: vpopuid and vpopgid are values taken from the vpopmail machine\'s /etc/passwd', - 'type' => 'textarea', - }, - - { - 'key' => 'vpopmailrestart', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, add a vpopmail export 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' => 'DEPRECATED, obsolete. Used to validate package definition setup and recur expressions against a preset list. Useful for webdemos, annoying to powerusers.', - 'type' => 'checkbox', - }, - - { - 'key' => 'username-colon', - 'section' => 'username', - 'description' => 'Allow the colon 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 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' => 'DEPRECATED, 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' => 'DEPRECATED, 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' => 'DEPRECATED, 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 Text::Template documentation for details on the template substitution language. The following variables are available
    • $username
    • $password
    • $first
    • $last
    • $pkg
    ', - '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 Text::Template documentation and the billing documentation for details on the template substitution language. A variable exists for each fieldname in the customer record ($first, $last, etc). The following additional variables are available
    • $payby - a friendler represenation of the field
    • $payinfo - the masked payment information
    • $expdate - the time at which the payment method expires (a UNIX timestamp)
    • $returnaddress - the invoice return address for this customer\'s agent
    ', - '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 Text::Template documentation for details on the template substitution language. The following variables are available
    • $username
    • $password
    • $first
    • $last
    • $pkg
    • $column
    • $amount
    • $threshold
    ', - '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' => 'deprecated', - 'description' => 'See batch-enable_payby and realtime-disable_payby. Used to (for CHEK): Cause 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' ], - }, - - #http://dev.coova.org/svn/coova-chilli/doc/dictionary.chillispot - { - 'key' => 'radius-chillispot-max', - 'section' => '', - 'description' => 'Enable ChilliSpot (and CoovaChilli) Max attributes, specifically ChilliSpot-Max-{Input,Output,Total}-{Octets,Gigawords}.', - 'type' => 'checkbox', - }, - - { - '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' => 'DEPRECATED, enable the Complimentary customer access right instead. Was: Usernames (Freeside users, created with freeside-adduser) which can create complimentary customers, one per line. If no usernames are entered, all users can create complimentary accounts.', - 'type' => 'textarea', - }, - - { - 'key' => 'credit_card-recurring_billing_flag', - 'section' => 'billing', - 'description' => 'This controls when the system passes the "recurring_billing" flag on credit card transactions. If supported by your processor (and the Business::OnlinePayment processor module), passing the flag indicates this is a recurring transaction and may turn off the CVV requirement. ', - 'type' => 'select', - 'select_hash' => [ - 'actual_oncard' => 'Default/classic behavior: set the flag if a customer has actual previous charges on the card.', - 'transaction_is_recur' => 'Set the flag if the transaction itself is recurring, irregardless of previous charges on the card.', - ], - }, - - { - 'key' => 'credit_card-recurring_billing_acct_code', - 'section' => 'billing', - 'description' => 'When the "recurring billing" flag is set, also set the "acct_code" to "rebill". Useful for reporting purposes with supported gateways (PlugNPay, others?)', - 'type' => 'checkbox', - }, - - { - '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. RT_Internal uses the built-in RT ticketing system (see the integrated ticketing installation instructions). RT_External 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, DBI:Pg:user=rt_user;password=rt_word;host=rt.example.com;dbname=rt', - 'type' => 'text', - - }, - - { - 'key' => 'ticket_system-rt_external_url', - 'section' => '', - 'description' => 'With external RT integration, the URL for the external RT installation, for example, https://rt.example.com/rt', - 'type' => 'text', - }, - - { - 'key' => 'company_name', - 'section' => 'required', - 'description' => 'Your company name', - 'type' => 'text', - }, - - { - 'key' => 'echeck-void', - 'section' => 'deprecated', - 'description' => 'DEPRECATED, 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' => 'DEPRECATED, 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' => 'DEPRECATED, 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' => '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 referred 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' => 'billing', - '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' => 'cust_bill-ftpformat', - 'section' => 'billing', - 'description' => 'Enable FTP of raw invoice data - format.', - 'type' => 'select', - 'select_enum' => [ '', 'default', 'billco', ], - }, - - { - 'key' => 'cust_bill-ftpserver', - 'section' => 'billing', - 'description' => 'Enable FTP of raw invoice data - server.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-ftpusername', - 'section' => 'billing', - 'description' => 'Enable FTP of raw invoice data - server.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-ftppassword', - 'section' => 'billing', - 'description' => 'Enable FTP of raw invoice data - server.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-ftpdir', - 'section' => 'billing', - 'description' => 'Enable FTP of raw invoice data - server.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-spoolformat', - 'section' => 'billing', - 'description' => 'Enable spooling of raw invoice data - format.', - 'type' => 'select', - 'select_enum' => [ '', 'default', 'billco', ], - }, - - { - 'key' => 'cust_bill-spoolagent', - 'section' => 'billing', - 'description' => 'Enable per-agent spooling of raw invoice data.', - 'type' => 'checkbox', - }, - - { - '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 https://secure.click2callu.com/)', - 'type' => 'text', - }, - { - 'key' => 'vonage-password', - 'section' => '', - 'description' => 'Vonage Click2Call username (see https://secure.click2callu.com/)', - 'type' => 'text', - }, - { - 'key' => 'vonage-fromnumber', - 'section' => '', - 'description' => 'Vonage Click2Call number (see https://secure.click2callu.com/)', - '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' => 'invoice-ship_address', - 'section' => 'billing', - 'description' => 'Enable this switch to include the ship address on the invoice.', - 'type' => 'checkbox', - }, - - { - 'key' => 'invoice-unitprice', - 'section' => 'billing', - 'description' => 'This switch enables unit pricing on the invoice.', - 'type' => 'checkbox', - }, - - { - 'key' => 'postal_invoice-fee_pkgpart', - 'section' => 'billing', - 'description' => 'This allows selection of a package to insert on invoices for customers with postal invoices selected.', - 'type' => 'select-sub', - 'options_sub' => sub { require FS::Record; - require FS::part_pkg; - map { $_->pkgpart => $_->pkg } - 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 : ''; - }, - }, - - { - 'key' => 'postal_invoice-recurring_only', - 'section' => 'billing', - 'description' => 'The postal invoice fee is omitted on invoices without recurring charges when this is set', - 'type' => 'checkbox', - }, - - { - 'key' => 'batch-enable', - 'section' => 'deprecated', #make sure batch-enable_payby is set for - #everyone before removing - 'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.', - 'type' => 'checkbox', - }, - - { - 'key' => 'batch-enable_payby', - 'section' => 'billing', - 'description' => 'Enable batch processing for the specified payment types.', - 'type' => 'selectmultiple', - 'select_enum' => [qw( CARD CHEK )], - }, - - { - 'key' => 'realtime-disable_payby', - 'section' => 'billing', - 'description' => 'Disable realtime processing for the specified payment types.', - 'type' => 'selectmultiple', - 'select_enum' => [qw( CARD CHEK )], - }, - - { - '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 Text::Template documentation for details on the template substitition language. Also see packages with a flat price plan The following variables are available
    • $packages allowing $packages->[0] thru $packages->[n]
    • $package the first package, same as $packages->[0]
    • $recurdates allowing $recurdates->[0] thru $recurdates->[n]
    • $recurdate the first recurdate, same as $recurdate->[0]
    • $first
    • $last
    ', -#
  • $payby
  • $expdate most likely only confuse - 'type' => 'textarea', - }, - - { - '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' => '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' => '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', - ], - }, - - { - 'key' => 'disable_previous_balance', - 'section' => 'billing', - 'description' => 'Disable inclusion of previous balance lines on invoices', - 'type' => 'checkbox', - }, - - { - 'key' => 'disable_acl_changes', - 'section' => '', - 'description' => 'Disable all ACL changes, for demos.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-edit_agent_custid', - 'section' => 'UI', - 'description' => 'Enable editing of the agent_custid field.', - 'type' => 'checkbox', - }, - - { - 'key' => 'cust_main-default_areacode', - 'section' => 'UI', - 'description' => 'Default area code for customers.', - 'type' => 'text', - }, - - { - 'key' => 'cust_bill-max_same_services', - 'section' => 'billing', - 'description' => 'Maximum number of the same service to list individually on invoices before condensing to a single line listing the number of services. Defaults to 5.', - 'type' => 'text', - }, - - { - 'key' => 'suspend_email_admin', - 'section' => '', - 'description' => 'Destination admin email address to enable suspension notices', - 'type' => 'text', - }, - - { - 'key' => 'email_report-subject', - 'section' => '', - 'description' => 'Subject for reports emailed by freeside-fetch. Defaults to "Freeside report".', - 'type' => 'text', - }, - - { - 'key' => 'sg-multicustomer_hack', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'checkbox', - }, - - { - 'key' => 'sg-ping_username', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'text', - }, - - { - 'key' => 'sg-ping_password', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'text', - }, - - { - 'key' => 'sg-login_username', - 'section' => '', - 'description' => "Don't use this.", - 'type' => 'text', - }, - - { - 'key' => 'queued-max_kids', - 'section' => '', - 'description' => 'Maximum number of queued processes. Defaults to 10.', - 'type' => 'text', - }, - - { - 'key' => 'cancelled_cust-noevents', - 'section' => 'billing', - 'description' => "Don't run events for cancelled customers", - 'type' => 'checkbox', - }, - - { - 'key' => 'svc_broadband-manage_link', - 'section' => 'UI', - 'description' => 'URL for svc_broadband "Manage Device" link. The following substitutions are available: $ip_addr.', - 'type' => 'text', - }, - -); - -1; - diff --git a/FS/FS/Cron/alert_expiration.pm b/FS/FS/Cron/alert_expiration.pm deleted file mode 100644 index eb53ea880..000000000 --- a/FS/FS/Cron/alert_expiration.pm +++ /dev/null @@ -1,189 +0,0 @@ -package FS::Cron::alert_expiration; - -use vars qw( @ISA @EXPORT_OK); -use Exporter; -use FS::Record qw(qsearch qsearchs); -use FS::Conf; -use FS::cust_main; -use FS::Misc; -use Time::Local; -use Date::Parse qw(str2time); - - -@ISA = qw( Exporter ); -@EXPORT_OK = qw( alert_expiration ); - -my $warning_time = 30 * 24 * 60 * 60; -my $urgent_time = 15 * 24 * 60 * 60; -my $panic_time = 5 * 24 * 60 * 60; -my $window_time = 24 * 60 * 60; - -sub alert_expiration { - my $conf = new FS::Conf; - my $smtpmachine = $conf->config('smtpmachine'); - - my %opt = @_; - my ($_date) = $opt{'d'} ? str2time($opt{'d'}) : $^T; - $_date += $opt{'y'} * 86400 if $opt{'y'}; - my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($_date)) [0..5]; - $mon++; - - my $debug = 0; - $debug = 1 if $opt{'v'}; - $debug = $opt{'l'} if $opt{'l'}; - - $FS::cust_main::DEBUG = $debug; - - # Get a list of customers. - - my %limit; - $limit{'agentnum'} = $opt{'a'} if $opt{'a'}; - $limit{'payby'} = $opt{'p'} if $opt{'p'}; - - my @customers; - - if(my @custnums = @ARGV) { - # We're given an explicit list of custnums, so select those. Then check against - # -a and -p to avoid doing anything unexpected. - foreach (@custnums) { - my $customer = FS::cust_main->by_key($_); - if($customer and (!$opt{'a'} or $customer->agentnum == $opt{'a'}) - and (!$opt{'p'} or $customer->payby eq $opt{'p'}) ) { - push @customers, $customer; - } - } - } - else { # no @ARGV - @customers = qsearch('cust_main', \%limit); - } - return if(!@customers); - foreach my $customer (@customers) { - next if !($customer->ncancelled_pkgs); # skip inactive customers - my $paydate = $customer->paydate; - next if $paydate =~ /^\s*$/; # skip empty expiration dates - - my $custnum = $customer->custnum; - my $first = $customer->first; - my $last = $customer->last; - my $company = $customer->company; - my $payby = $customer->payby; - my $payinfo = $customer->payinfo; - my $daytime = $customer->daytime; - my $night = $customer->night; - - my ($paymonth, $payyear) = $customer->paydate_monthyear; - $paymonth--; # localtime() convention - $payday = 1; # This is enforced by FS::cust_main::check. - my $expire_time; - if($payby eq 'CARD' || $payby eq 'DCRD') { - # Credit cards expire at the end of the month/year. - if($paymonth == 11) { - $payyear++; - $paymonth = 0; - } else { - $paymonth++; - } - $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear) - 1; - } - else { - $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear); - } - - if (grep { $expire_time < $_date + $_ && - $expire_time > $_date + $_ - $window_time } - ($warning_time, $urgent_time, $panic_time) ) { - # Send an expiration notice. - my $agentnum = $customer->agentnum; - my $error = ''; - - my $msgnum = $conf->config('alerter_msgnum', $agentnum); - if ( $msgnum ) { # new hotness - my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ); - $customer->setfield('expdate', $expire_time); - $error = $msg_template->send('cust_main' => $customer); - } - else { #!$msgnum, the hard way - $mail_sender = $conf->config('invoice_from', $agentnum); - $failure_recipient = $conf->config('invoice_from', $agentnum) - || 'postmaster'; - - my @alerter_template = $conf->config('alerter_template', $agentnum) - or die 'cannot load config file alerter_template'; - - my $alerter = new Text::Template(TYPE => 'ARRAY', - SOURCE => [ - map "$_\n", @alerter_template - ]) - or die "can't create Text::Template object: $Text::Template::ERROR"; - - $alerter->compile() - or die "can't compile template: $Text::Template::ERROR"; - - my @invoicing_list = $customer->invoicing_list; - my @to_addrs = grep { $_ ne 'POST' } @invoicing_list; - if(@to_addrs) { - # Set up template fields. - my %fill_in; - $fill_in{$_} = $customer->getfield($_) - foreach(qw(first last company)); - $fill_in{'expdate'} = $expire_time; - $fill_in{'company_name'} = $conf->config('company_name', $agentnum); - $fill_in{'company_address'} = - join("\n",$conf->config('company_address',$agentnum))."\n"; - if($payby eq 'CARD' || $payby eq 'DCRD') { - $fill_in{'payby'} = "credit card (". - substr($customer->payinfo, 0, 2) . "xxxxxxxxxx" . - substr($payinfo, -4) . ")"; - } - elsif($payby eq 'COMP') { - $fill_in{'payby'} = 'complimentary account'; - } - else { - $fill_in{'payby'} = 'current method'; - } - # Send it already! - $error = FS::Misc::send_email ( - from => $mail_sender, - to => [ @to_addrs ], - subject => 'Billing Arrangement Expiration', - body => [ $alerter->fill_in( HASH => \%fill_in ) ], - ); - } - else { # if(@to_addrs) - push @{$agent_failure_body{$customer->agentnum}}, - sprintf(qq{%5d %-32.32s %4s %10s %12s %12s}, - $custnum, - $first . " " . $last . " " . $company, - $payby, - $paydate, - $daytime, - $night ); - } - } # if($msgnum) - -# should we die here rather than report failure as below? - die "can't send expiration alert: $error" - if $error; - - } # if(expired) - } # foreach(@customers) - - # Failure notification - foreach my $agentnum (keys %agent_failure_body) { - $mail_sender = $conf->config('invoice_from', $agentnum) - if($conf->exists('invoice_from', $agentnum)); - $failure_recipient = $conf->config('invoice_from', $agentnum) - if($conf->exists('invoice_from', $agentnum)); - my $error = FS::Misc::send_email ( - from => $mail_sender, - to => $failure_recipient, - subject => 'Unnotified Billing Arrangement Expirations', - body => [ @{$agent_failure_body{$agentnum}} ], - ); - die "can't send alerter failure email to $failure_recipient: $error" - if $error; - } - -} - -1; diff --git a/FS/FS/Cron/backup.pm b/FS/FS/Cron/backup.pm deleted file mode 100644 index 9d8826120..000000000 --- a/FS/FS/Cron/backup.pm +++ /dev/null @@ -1,45 +0,0 @@ -package FS::Cron::backup; - -use strict; -use vars qw( @ISA @EXPORT_OK ); -use Exporter; -use Date::Format; -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 ) { - $dest .= time2str('/%Y%m%d%H%M%S',time); - 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.gpg"); - unlink "/var/tmp/$database.gpg" or die $!; - } else { - chmod 0600, '/var/tmp/$database.sql'; - scp("/var/tmp/$database.sql", "$dest.sql"); - } - 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 7388733d4..000000000 --- a/FS/FS/Cron/bill.pm +++ /dev/null @@ -1,245 +0,0 @@ -package FS::Cron::bill; - -use strict; -use vars qw( @ISA @EXPORT_OK ); -use Exporter; -use Date::Parse; -use DBI 1.33; #The "clone" method was added in DBI 1.33. -use FS::UID qw( dbh driver_name ); -use FS::Record qw( qsearch qsearchs ); -use FS::queue; -use FS::cust_main; -use FS::part_event; -use FS::part_event_condition; - -@ISA = qw( Exporter ); -@EXPORT_OK = qw ( bill bill_where ); - -#freeside-daily %opt: -# -s: re-charge setup fees -# -v: enable debugging -# -l: debugging level -# -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing. -# -r: Multi-process mode dry run option -# -g: Don't bill these pkgparts - -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 $conf = new FS::Conf; - if ( $conf->exists('disable_cron_billing') ) { - warn "disable_cron_billing set, skipping billing\n" if $debug; - return; - } - - #we're at now now (and later). - $opt{'time'} = $opt{'d'} ? str2time($opt{'d'}) : $^T; - $opt{'time'} += $opt{'y'} * 86400 if $opt{'y'}; - - $opt{'invoice_time'} = $opt{'n'} ? $^T : $opt{'time'}; - - #hashref here doesn't work with -m - #my $not_pkgpart = $opt{g} ? { map { $_=>1 } split(/,\s*/, $opt{g}) } - # : {}; - - ### - # get a list of custnums - ### - - my $cursor_dbh = dbh->clone; - - my $select = 'SELECT custnum FROM cust_main WHERE '. bill_where( %opt ); - - unless ( driver_name =~ /^mysql/ ) { - $cursor_dbh->do( "DECLARE cron_bill_cursor CURSOR FOR $select" ) - or die $cursor_dbh->errstr; - } - - while ( 1 ) { - - my $sql = (driver_name =~ /^mysql/) - ? $select - : 'FETCH 100 FROM cron_bill_cursor'; - - my $sth = $cursor_dbh->prepare($sql); - - $sth->execute or die $sth->errstr; - - my @custnums = map { $_->[0] } @{ $sth->fetchall_arrayref }; - - last unless scalar(@custnums); - - ### - # 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 ) { - - my %args = ( - 'time' => $opt{'time'}, - 'invoice_time' => $opt{'invoice_time'}, - 'actual_time' => $^T, #when freeside-bill was started - #(not, when using -m, freeside-queued) - 'check_freq' => $check_freq, - 'resetup' => ( $opt{'s'} ? $opt{'s'} : 0 ), - 'not_pkgpart' => $opt{'g'}, #$not_pkgpart, - ); - - if ( $opt{'m'} ) { - - if ( $opt{'r'} ) { - warn "DRY RUN: would add custnum $custnum for queued_bill\n"; - } else { - - #avoid queuing another job if there's one still waiting to run - next if qsearch( 'queue', { 'job' => 'FS::cust_main::queued_bill', - 'custnum' => $custnum, - 'status' => 'new', - } - ); - - #add job to queue that calls bill_and_collect with options - my $queue = new FS::queue { - 'job' => 'FS::cust_main::queued_bill', - 'secure' => 'Y', - 'priority' => 99, #don't get in the way of provisioning jobs - }; - my $error = $queue->insert( 'custnum'=>$custnum, %args ); - - } - - } else { - - my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ); - $cust_main->bill_and_collect( %args, 'debug' => $debug ); - - } - - } - - last if driver_name =~ /^mysql/; - - } - - $cursor_dbh->commit or die $cursor_dbh->errstr; - -} - -# freeside-daily %opt: -# -d: Pretend it's 'date'. Date is in any format Date::Parse is happy with, -# but be careful. -# -# -y: In addition to -d, which specifies an absolute date, the -y switch -# specifies an offset, in days. For example, "-y 15" would increment the -# "pretend date" 15 days from whatever was specified by the -d switch -# (or now, if no -d switch was given). -# -# -n: When used with "-d" and/or "-y", specifies that invoices should be dated -# with today's date, irregardless of the pretend date used to pre-generate -# the invoices. -# -# -p: Only process customers with the specified payby (I, I, I, I, I, I, I) -# -# -a: Only process customers with the specified agentnum -# -# -v: enable debugging -# -# -l: debugging level - -sub bill_where { - my( %opt ) = @_; - - my $time = $opt{'time'}; - my $invoice_time = $opt{'invoice_time'}; - - my $check_freq = $opt{'check_freq'} || '1d'; - - my @search = (); - - push @search, "( cust_main.archived != 'Y' OR archived IS NULL )"; #disable? - - push @search, "cust_main.payby = '". $opt{'p'}. "'" - if $opt{'p'}; - push @search, "cust_main.agentnum IN ( ". $opt{'a'}. " ) " - if $opt{'a'}; - - #it would be useful if i recognized $opt{g} / $not_pkgpart... - - if ( @ARGV ) { - push @search, "( ". - join(' OR ', map "cust_main.custnum = $_", @ARGV ). - " )"; - } - - ### - # generate where_pkg/where_event search clause - ### - - # select * from cust_main where - my $where_pkg = <<"END"; - EXISTS( - SELECT 1 FROM cust_pkg - WHERE cust_main.custnum = cust_pkg.custnum - AND ( cancel IS NULL OR cancel = 0 ) - AND ( ( ( setup IS NULL OR setup = 0 ) - AND ( start_date IS NULL OR start_date = 0 - OR ( start_date IS NOT NULL AND start_date <= $^T ) - ) - ) - OR bill IS NULL OR bill <= $time - OR ( expire IS NOT NULL AND expire <= $^T ) - OR ( adjourn IS NOT NULL AND adjourn <= $^T ) - ) - ) -END - - #some false laziness w/cust_main::Billing due_cust_event - 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, - ); - $where = $where ? "AND $where" : ''; - - my $are_part_event = - "EXISTS ( SELECT 1 FROM part_event $join - WHERE check_freq = '$check_freq' - AND eventtable = '$eventtable' - AND ( disabled = '' OR disabled IS NULL ) - $where - ) - "; - - if ( $eventtable eq 'cust_main' ) { - $are_part_event; - } else { - "EXISTS ( SELECT 1 FROM $eventtable - WHERE cust_main.custnum = $eventtable.custnum - AND $are_part_event - ) - "; - } - - } FS::part_event->eventtables); - - push @search, "( $where_pkg OR $where_event )"; - - warn "searching for customers:\n". join("\n", @search). "\n" - if $opt{'v'} || $opt{'l'}; - - join(' AND ', @search); - -} - -1; diff --git a/FS/FS/Cron/breakage.pm b/FS/FS/Cron/breakage.pm deleted file mode 100644 index 6dd904d6a..000000000 --- a/FS/FS/Cron/breakage.pm +++ /dev/null @@ -1,84 +0,0 @@ -package FS::Cron::breakage; - -use strict; -use base 'Exporter'; -use vars qw( @EXPORT_OK ); -use FS::Conf; -use FS::Record qw(qsearch); -use FS::agent; -use FS::cust_main; - -@EXPORT_OK = qw ( reconcile_breakage ); - -#freeside-daily %opt -# -v: enable debugging -# -l: debugging level - -sub reconcile_breakage { - my %opt = @_; - - my $conf = new FS::Conf; - - foreach my $agent (qsearch('agent', {})) { - - my $days = $conf->config('breakage-days', $agent->agentnum) - or next; - - my $since = int( $^T - ($days * 86400) ); - - warn 'searching '. $agent->agent. " for customers with unapplied payments more than $days days old\n" - if $opt{'v'}; - - #find customers w/negative balance older than $days (and no activity since) - # and no activity (invoices/payments/credits/refunds) newer than $since - # (XXX except antother breakage invoice???) - - my $extra_sql = - ' AND 0 > '. FS::cust_main->balance_sql. - ' AND '. join(' AND ', map { - " NOT EXISTS ( - SELECT 1 FROM $_ - WHERE $_.custnum = cust_main.custnum - AND _date >= $since - ) " - } qw( cust_bill cust_pay cust_credit cust_refund ) - ); - - my @customers = qsearch({ - 'table' => 'cust_main', - 'hashref' => { 'agentnum' => $agent->agentnum, - 'payby' => { op=>'!=', value=>'COMP', }, - }, - 'extra_sql' => $extra_sql, - }); - - #and then create a "breakage" charge & invoice for them - - foreach my $cust_main ( @customers ) { - - warn 'reconciling breakage for customer '. $cust_main->custnum. - ': '. $cust_main->name. "\n" - if $opt{'v'}; - - my $error = - $cust_main->charge({ - 'amount' => sprintf('%.2f', 0 - $cust_main->balance ), - 'pkg' => 'Breakage', - 'comment' => 'breakage reconciliation', - 'classnum' => scalar($conf->config('breakage-pkg_class')), - 'setuptax' => 'Y', - 'bill_now' => 1, - }) - || $cust_main->apply_payments_and_credits; - - if ( $error ) { - warn "error charging for breakage reconciliation: $error\n"; - } - - } - - } - -} - -1; diff --git a/FS/FS/Cron/check.pm b/FS/FS/Cron/check.pm deleted file mode 100644 index 9d3ffbdbd..000000000 --- a/FS/FS/Cron/check.pm +++ /dev/null @@ -1,200 +0,0 @@ -package FS::Cron::check; - -use strict; -use vars qw( @ISA @EXPORT_OK $DEBUG $FS_RUN $error_msg - $SELFSERVICE_USER $SELFSERVICE_MACHINES @SELFSERVICE_MACHINES - ); -use Exporter; -use LWP::UserAgent; -use HTTP::Request; -use URI::Escape; -use Email::Send; -use FS::Conf; -use FS::Record qw(qsearch); -use FS::cust_pay_pending; - -@ISA = qw( Exporter ); -@EXPORT_OK = qw( - check_queued check_selfservice check_apache check_bop_failures - check_sg check_sg_login check_sgng - alert error_msg -); - -$DEBUG = 0; - -$FS_RUN = '/var/run'; - -sub check_queued { - _check_fsproc('queued'); -} - -$SELFSERVICE_USER = '%%%SELFSERVICE_USER%%%'; - -$SELFSERVICE_MACHINES = '%%%SELFSERVICE_MACHINES%%%'; #substituted by Makefile -$SELFSERVICE_MACHINES =~ s/^\s+//; -$SELFSERVICE_MACHINES =~ s/\s+$//; -@SELFSERVICE_MACHINES = split(/\s+/, $SELFSERVICE_MACHINES); -@SELFSERVICE_MACHINES = () - if scalar(@SELFSERVICE_MACHINES) == 1 - && $SELFSERVICE_MACHINES[0] eq '%%%'.'SELFSERVICE_MACHINES'.'%%%'; - -sub check_selfservice { - foreach my $machine ( @SELFSERVICE_MACHINES ) { - unless ( _check_fsproc("selfservice-server.$SELFSERVICE_USER.$machine") ) { - $error_msg = "Self-service daemon not running for $machine"; - return 0; - } - } - return 1; -} - -sub check_sg { - my $conf = new FS::Conf; - #different trigger if they ever stop using multicustomer_hack ? - return 1 unless $conf->exists('sg-multicustomer_hack'); - - my $ua = new LWP::UserAgent; - $ua->agent("FreesideCronCheck/0.1 " . $ua->agent); - - my $USER = $conf->config('sg-ping_username'); - my $PASS = $conf->config('sg-ping_password'); - my $req = new HTTP::Request GET=>"https://$USER:$PASS\@localhost/sg/ping.cgi"; - my $res = $ua->request($req); - - return 1 if $res->is_success - && $res->content =~ /OK/ - && $res->content !~ /error/i; #doh, the error message includes "OK" - - $error_msg = $res->is_success ? $res->content : $res->status_line; - return 0; -} - -sub check_sg_login { - my $conf = new FS::Conf; - #different trigger if they ever stop using multicustomer_hack ? - return 1 unless $conf->exists('sg-multicustomer_hack'); - - my $ua = new LWP::UserAgent; - $ua->agent("FreesideCronCheck/0.1 " . $ua->agent); - - my $USER = $conf->config('sg-ping_username'); - my $PASS = $conf->config('sg-ping_password'); - my $USERNAME = $conf->config('sg-login_username'); - my $req = new HTTP::Request - GET=>"https://$USER:$PASS\@localhost/sg/start.cgi?". - 'username='. uri_escape($USERNAME); - my $res = $ua->request($req); - - return 1 if $res->is_success - && $res->content =~ /[\da-f]{32}/i #session_id - && $res->content !~ /error/i; - - $error_msg = $res->is_success ? $res->content : $res->status_line; - return 0; -} - -sub check_sgng { - my $conf = new FS::Conf; - #different trigger if they ever stop using multicustomer_hack ? - return 1 unless $conf->exists('sg-multicustomer_hack'); - - eval 'use RPC::XML; use RPC::XML::Client;'; - if ($@) { $error_msg = $@; return 0; }; - - my $cli = RPC::XML::Client->new('https://localhost/selfservice/xmlrpc.cgi'); - my $resp = $cli->send_request('FS.SelfService.XMLRPC.ping'); - - return 1 if ref($resp) - && ! $resp->is_fault - && ref($resp->value) - && $resp->value->{'pong'} == 1; - - #hua - $error_msg = ref($resp) - ? ( $resp->is_fault - ? $resp->string - : ( ref($resp->value) ? $resp->value->{'error'} - : $resp->value - ) - ) - : $resp; - return 0; -} - -sub _check_fsproc { - my $arg = shift; - _check_pidfile( "freeside-$arg.pid" ); -} - -sub _check_pidfile { - my $pidfile = shift; - open(PID, "$FS_RUN/$pidfile") or return 0; - chomp( my $pid = scalar() ); - close PID; # or return 0; - - $pid && kill 0, $pid; -} - -sub check_apache { - my $ua = new LWP::UserAgent; - $ua->agent("FreesideCronCheck/0.1 " . $ua->agent); - - my $req = new HTTP::Request GET => 'https://localhost/'; - my $res = $ua->request($req); - - return 1 if $res->is_success || $res->status_line =~ /^403/; - $error_msg = $res->status_line; - return 0; - -} - -#and now for something entirely different... -my $num_consecutive_bop_failures = 60; -sub check_bop_failures { - - return 1 if grep { $_->statustext eq 'captured' } - qsearch({ - 'table' => 'cust_pay_pending', - 'hashref' => { 'status' => 'done' }, - 'order_by' => 'ORDER BY paypendingnum DESC'. - " LIMIT $num_consecutive_bop_failures", - }); - $error_msg = "Last $num_consecutive_bop_failures real-time payments failed"; - return 0; -} - -# - -sub error_msg { - $error_msg; -} - -sub alert { - my( $alert, @emails ) = @_; - - my $conf = new FS::Conf; - my $smtpmachine = $conf->config('smtpmachine'); - my $company_name = $conf->config('company_name'); - - foreach my $email (@emails) { - warn "warning $email about $alert\n" if $DEBUG; - - my $message = <<"__MESSAGE__"; -From: support\@freeside.biz -To: $email -Subject: FREESIDE ALERT for $company_name - -FREESIDE ALERT: $alert - -__MESSAGE__ - - my $sender = Email::Send->new({ mailer => 'SMTP' }); - $sender->mailer_args([ Host => $smtpmachine ]); - $sender->send($message); - - } - -} - -1; - diff --git a/FS/FS/Cron/expire_user_pref.pm b/FS/FS/Cron/expire_user_pref.pm deleted file mode 100644 index 32269271e..000000000 --- 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 3d427b234..000000000 --- a/FS/FS/Cron/notify.pm +++ /dev/null @@ -1,159 +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 qsearchs); -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 $conf = new FS::Conf; - my $error = ''; - - 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 CAST( part_pkg_option.optionvalue AS $integer ) > 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 CAST( cust_pkg_option.optionvalue AS $integer ) = 1 - ) -END - - if ($opt{a}) { - $where_pkg .= <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 $msgnum = $conf->config('impending_recur_msgnum',$cust_main->agentnum); - if ( $msgnum ) { - my $msg_template = qsearchs('msg_template', { msgnum => $msgnum }); - $cust_main->setfield('packages', \\@packages); - $cust_main->setfield('recurdates', \\@recurdates); - $error = $msg_template->send('cust_main' => $cust_main); - } - else { - $error = $cust_main->notify( 'impending_recur_template', - 'extra_fields' => { 'packages' => \@packages, - 'recurdates' => \@recurdates, - 'package' => $packages[0], - 'recurdate' => $recurdates[0], - }, - ); - } #if $msgnum - 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/upload.pm b/FS/FS/Cron/upload.pm deleted file mode 100644 index fea3d2cc7..000000000 --- a/FS/FS/Cron/upload.pm +++ /dev/null @@ -1,176 +0,0 @@ -package FS::Cron::upload; - -use strict; -use vars qw( @ISA @EXPORT_OK $me $DEBUG ); -use Exporter; -use Date::Format; -use FS::UID qw(dbh); -use FS::Record qw( qsearch qsearchs ); -use FS::Conf; -use FS::queue; -use FS::agent; -use LWP::UserAgent; -use HTTP::Request; -use HTTP::Request::Common; -use HTTP::Response; - -@ISA = qw( Exporter ); -@EXPORT_OK = qw ( upload ); -$DEBUG = 0; -$me = '[FS::Cron::upload]'; - -#freeside-daily %opt: -# -v: enable debugging -# -l: debugging level -# -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing. -# -r: Multi-process mode dry run option -# -a: Only process customers with the specified agentnum - - -sub upload { - my %opt = @_; - - my $debug = 0; - $debug = 1 if $opt{'v'}; - $debug = $opt{'l'} if $opt{'l'}; - - local $DEBUG = $debug if $debug; - - warn "$me upload called\n" if $DEBUG; - - my $conf = new FS::Conf; - my @agent = grep { $conf->config( 'billco-username', $_->agentnum, 1 ) } - grep { $conf->config( 'billco-password', $_->agentnum, 1 ) } - qsearch( 'agent', {} ); - - my $date = time2str('%Y%m%d%H%M%S', $^T); # more? - - @agent = grep { $_ == $opt{'a'} } @agent if $opt{'a'}; - - foreach my $agent ( @agent ) { - - my $agentnum = $agent->agentnum; - - if ( $opt{'m'} ) { - - if ( $opt{'r'} ) { - warn "DRY RUN: would add agent $agentnum for queued upload\n"; - } else { - - my $queue = new FS::queue { - 'job' => 'FS::Cron::upload::billco_upload', - }; - my $error = $queue->insert( - 'agentnum' => $agentnum, - 'date' => $date, - 'l' => $opt{'l'} || '', - 'm' => $opt{'m'} || '', - 'v' => $opt{'v'} || '', - ); - - } - - } else { - - eval "&billco_upload( 'agentnum' => $agentnum, 'date' => $date );"; - warn "billco_upload failed: $@\n" - if ( $@ ); - - } - - } - -} - -sub billco_upload { - my %opt = @_; - - warn "$me billco_upload called\n" if $DEBUG; - my $conf = new FS::Conf; - my $dir = '%%%FREESIDE_EXPORT%%%/export.'. $FS::UID::datasrc. '/cust_bill'; - - my $agentnum = $opt{agentnum} or die "no agentnum provided\n"; - my $url = $conf->config( 'billco-url', $agentnum ) - or die "no url for agent $agentnum\n"; - my $username = $conf->config( 'billco-username', $agentnum, 1 ) - or die "no username for agent $agentnum\n"; - my $password = $conf->config( 'billco-password', $agentnum, 1 ) - or die "no password for agent $agentnum\n"; - my $clicode = $conf->config( 'billco-clicode', $agentnum ) - or die "no clicode for agent $agentnum\n"; - - die "no date provided\n" unless $opt{date}; - my $zipfile = "$dir/agentnum$agentnum-$opt{date}.zip"; - - 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 $agent = qsearchs( 'agent', { agentnum => $agentnum } ) - or die "no such agent: $agentnum"; - $agent->select_for_update; #mutex - - unless ( -f "$dir/agentnum$agentnum-header.csv" || - -f "$dir/agentnum$agentnum-detail.csv" ) - { - warn "$me neither $dir/agentnum$agentnum-header.csv nor ". - "$dir/agentnum$agentnum-detail.csv found\n" if $DEBUG; - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - return; - } - - # a better way? - if ($opt{m}) { - my $sql = "SELECT count(*) FROM queue LEFT JOIN cust_main USING(custnum) ". - "WHERE queue.job='FS::cust_main::queued_bill' AND cust_main.agentnum = ?"; - my $sth = $dbh->prepare($sql) or die $dbh->errstr; - while (1) { - $sth->execute( $agentnum ) - or die "Unexpected error executing statement $sql: ". $sth->errstr; - last if $sth->fetchow_arrayref->[0]; - sleep 300; - } - } - - foreach ( qw ( header detail ) ) { - rename "$dir/agentnum$agentnum-$_.csv", - "$dir/agentnum$agentnum-$opt{date}-$_.csv"; - } - - my $command = "cd $dir; zip $zipfile ". - "agentnum$agentnum-$opt{date}-header.csv ". - "agentnum$agentnum-$opt{date}-detail.csv"; - - system($command) and die "$command failed\n"; - - unlink "agentnum$agentnum-$opt{date}-header.csv", - "agentnum$agentnum-$opt{date}-detail.csv"; - - my $ua = new LWP::UserAgent; - my $res = $ua->request( POST( $url, - 'Content_Type' => 'form-data', - 'Content' => [ 'username' => $username, - 'pass' => $password, - 'custid' => $username, - 'clicode' => $clicode, - 'file1' => [ $zipfile ], - ], - ) - ); - - die "upload failed: ". $res->status_line. "\n" - unless $res->is_success; - - $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 075572d50..000000000 --- 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 bcd337d2c..000000000 --- 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 b58cde49f..000000000 --- a/FS/FS/Daemon.pm +++ /dev/null @@ -1,120 +0,0 @@ -package FS::Daemon; - -use vars qw( @ISA @EXPORT_OK ); -use vars qw( $pid_dir $me $pid_file $sigint $sigterm $NOSIG $logfile ); -use Exporter; -use Fcntl qw(:flock); -use POSIX qw(setsid); -use IO::File; -use File::Basename; -use File::Slurp qw(slurp); -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 myexit logfile sigint sigterm -); -%EXPORT_TAGS = ( 'all' => [ @EXPORT_OK ] ); - -$pid_dir = '/var/run'; - -$NOSIG = 0; -$PID_NEWSTYLE = 0; - -sub daemonize1 { - $me = shift; - - $pid_file = $pid_dir; - if ( $PID_NEWSTYLE ) { - $pid_file .= '/freeside'; - mkdir $pid_file unless -d $pid_file; - chown $FS::UID::freeside_uid, -1, $pid_file; - } - $pid_file .= "/$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; - chown $FS::UID::freeside_uid, -1, $pid_file; - print $pidfh "$pid\n"; - exit; - } - - #sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; } - #$SIG{CHLD} = \&REAPER; - $sigterm = 0; - $sigint = 0; - unless ( $NOSIG ) { - $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 myexit { - chomp( my $pid = slurp($pid_file) ); - unlink $pid_file if -e $pid_file && $$ == $pid; - exit; -} - -sub _die { - die @_ if $^S; # $^S = 1 during an eval(), don't break exception handling - my $msg = shift; - - chomp( my $pid = slurp($pid_file) ); - unlink $pid_file if -e $pid_file && $$ == $pid; - - _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; -} - -1; diff --git a/FS/FS/InitHandler.pm b/FS/FS/InitHandler.pm deleted file mode 100644 index 5038cf352..000000000 --- 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 () { - 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/Maestro.pm b/FS/FS/Maestro.pm deleted file mode 100644 index 84f278c2f..000000000 --- a/FS/FS/Maestro.pm +++ /dev/null @@ -1,248 +0,0 @@ -package FS::Maestro; - -use strict; -use Date::Format; -use FS::Conf; -use FS::Record qw( qsearchs ); -use FS::cust_main; -use FS::cust_pkg; -use FS::part_svc; - -#i guess this is kind of deprecated in favor of service_status, but keeping it -#around until they say they don't need it. -sub customer_status { - my( $custnum ) = shift; #@_; - my $svcnum = @_ ? shift : ''; - - my $curuser = $FS::CurrentUser::CurrentUser; - - my $cust_main = qsearchs({ - 'table' => 'cust_main', - 'hashref' => { 'custnum' => $custnum }, - 'extra_sql' => ' AND '. $curuser->agentnums_sql, - }) - or return { 'status' => 'E', - 'error' => "custnum $custnum not found" }; - - return service_status($svcnum) if $svcnum; - - ### - # regular customer to maestro (single package) - ### - - my %result = (); - - my @cust_pkg = $cust_main->cust_pkg; - - #things specific to the non-reseller scenario - - $result{'status'} = substr($cust_main->ucfirst_status,0,1); - - $result{'products'} = - [ map $_->pkgpart, grep !$_->get('cancel'), @cust_pkg ]; - - #find svc_pbx - - my @cust_svc = map $_->cust_svc, @cust_pkg; - - my @cust_svc_pbx = - grep { my($n,$l,$t) = $_->label; $t eq 'svc_pbx' } - @cust_svc; - - if ( ! @cust_svc_pbx ) { - return { 'status' => 'E', - 'error' => "customer $custnum has no conference service" }; - } elsif ( scalar(@cust_svc_pbx) > 1 ) { - return { 'status' => 'E', - 'error' => - "customer $custnum has more than one conference". - " service (reseller?); specify a svcnum as a second argument", - }; - } - - my $cust_svc_pbx = $cust_svc_pbx[0]; - - my $svc_pbx = $cust_svc_pbx->svc_x; - - # find "outbound service" y/n - - my $conf = new FS::Conf; - my %outbound_pkgs = map { $_=>1 } $conf->config('mc-outbound_packages'); - $result{'outbound_service'} = - scalar( grep { $outbound_pkgs{ $_->pkgpart } - && !$_->get('cancel') - } - @cust_pkg - ) - ? 1 : 0; - - # find "good till" date/time stamp - - my @active_cust_pkg = - sort { $a->bill <=> $b->bill } - grep { !$_->get('cancel') && $_->part_pkg->freq ne '0' } - @cust_pkg; - $result{'good_till'} = time2str('%c', $active_cust_pkg[0]->bill || time ); - - return { - 'name' => $cust_main->name, - 'email' => $cust_main->invoicing_list_emailonly_scalar, - #'agentnum' => $cust_main->agentnum, - #'agent' => $cust_main->agent->agent, - 'max_lines' => $svc_pbx ? $svc_pbx->max_extensions : '', - 'max_simultaneous' => $svc_pbx ? $svc_pbx->max_simultaneous : '', - %result, - }; - -} - -sub service_status { - my $svcnum = shift; - - my $svc_pbx = qsearchs({ - 'table' => 'svc_pbx', - 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. - ' LEFT JOIN cust_pkg USING ( pkgnum ) ', - 'hashref' => { 'svcnum' => $svcnum }, - #'extra_sql' => " AND custnum = $custnum", - }) - or return { 'status' => 'E', - 'error' => "svcnum $svcnum not found" }; - - my $cust_pkg = $svc_pbx->cust_svc->cust_pkg; - my $cust_main = $cust_pkg->cust_main; - - my %result = (); - - #status in the reseller scenario - $result{'status'} = substr($cust_pkg->ucfirst_status,0,1); - - # find "outbound service" y/n - my @cust_pkg = $cust_main->cust_pkg; - #XXX what about outbound service per-reseller ? - my $conf = new FS::Conf; - my %outbound_pkgs = map { $_=>1 } $conf->config('mc-outbound_packages'); - $result{'outbound_service'} = - scalar( grep { $outbound_pkgs{ $_->pkgpart } - && !$_->get('cancel') - } - @cust_pkg - ) - ? 1 : 0; - - # find "good till" date/time stamp (this package) - $result{'good_till'} = time2str('%c', $cust_pkg->bill || time ); - - return { - 'custnum' => $cust_main->custnum, - 'name' => $cust_main->name, - 'email' => $cust_main->invoicing_list_emailonly_scalar, - #'agentnum' => $cust_main->agentnum, - #'agent' => $cust_main->agent->agent, - 'max_lines' => $svc_pbx->max_extensions, - 'max_simultaneous' => $svc_pbx->max_simultaneous, - %result, - }; - -} - -#some false laziness w/ MyAccount order_pkg -sub order_pkg { - my $opt = ref($_[0]) ? shift : { @_ }; - - $opt->{'title'} = delete $opt->{'name'} - if !exists($opt->{'title'}) && exists($opt->{'name'}); - - my $custnum = $opt->{'custnum'}; - - my $curuser = $FS::CurrentUser::CurrentUser; - - my $cust_main = qsearchs({ - 'table' => 'cust_main', - 'hashref' => { 'custnum' => $custnum }, - 'extra_sql' => ' AND '. $curuser->agentnums_sql, - }) - or return { 'error' => "custnum $custnum not found" }; - - my $status = $cust_main->status; - #false laziness w/ClientAPI/Signup.pm - - my $cust_pkg = new FS::cust_pkg ( { - 'custnum' => $custnum, - 'pkgpart' => $opt->{'pkgpart'}, - } ); - my $error = $cust_pkg->check; - return { 'error' => $error } if $error; - - my @svc = (); - unless ( $opt->{'svcpart'} eq 'none' ) { - - my $svcpart = ''; - if ( $opt->{'svcpart'} =~ /^(\d+)$/ ) { - $svcpart = $1; - } else { - $svcpart = $cust_pkg->part_pkg->svcpart; #($svcdb); - } - - my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } ); - return { 'error' => "Unknown svcpart $svcpart" } unless $part_svc; - - my $svcdb = $part_svc->svcdb; - - my %fields = ( - 'svc_acct' => [ qw( username domsvc _password sec_phrase popnum ) ], - 'svc_domain' => [ qw( domain ) ], - 'svc_phone' => [ qw( phonenum pin sip_password phone_name ) ], - 'svc_external' => [ qw( id title ) ], - 'svc_pbx' => [ qw( id title ) ], - ); - - my $svc_x = "FS::$svcdb"->new( { - 'svcpart' => $svcpart, - map { $_ => $opt->{$_} } @{$fields{$svcdb}} - } ); - - #snarf processing not necessary here (or probably at all, anymore) - - 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; - -# currently they're using this in the reseller scenario, so don't -# bill the package immediately -# 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; -# } - - my $svcnum = $svc[0] ? $svc[0]->svcnum : ''; - - return { error=>'', pkgnum=>$cust_pkg->pkgnum, svcnum=>$svcnum }; - -} - -1; diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm deleted file mode 100644 index 9b010e8d6..000000000 --- a/FS/FS/Mason.pm +++ /dev/null @@ -1,555 +0,0 @@ -package FS::Mason; - -use strict; -use vars qw( @ISA @EXPORT_OK $addl_handler_use ); -use Exporter; -use Carp; -use File::Slurp qw( slurp ); -use HTML::Mason 1.27; #http://www.masonhq.com/?ApacheModPerl2Redirect -use HTML::Mason::Interp; -use HTML::Mason::Compiler::ToObject; - -@ISA = qw( Exporter ); -@EXPORT_OK = qw( mason_interps ); - -=head1 NAME - -FS::Mason - Initialize the Mason environment - -=head1 SYNOPSIS - - use FS::Mason qw( mason_interps ); - - my( $fs_interp, $rt_interp ) = mason_interps('apache'); - - #OR - - my( $fs_interp, $rt_interp ) = mason_interps('standalone'); #XXX name? - -=head1 DESCRIPTION - -Initializes the Mason environment, loads all Freeside and RT libraries, etc. - -=cut - -$addl_handler_use = ''; -my $addl_handler_use_file = '%%%FREESIDE_CONF%%%/addl_handler_use.pl'; -if ( -e $addl_handler_use_file ) { - $addl_handler_use = slurp( $addl_handler_use_file ); -} - -# List of modules that you want to use from components (see Admin -# manual for details) -{ - package HTML::Mason::Commands; - - use strict; - use vars qw( %session ); - use CGI 3.29 qw(-private_tempfiles); #3.29 to fix RT attachment problems - - #breaks quick payment entry - #http://rt.cpan.org/Public/Bug/Display.html?id=37365 - die "CGI.pm v3.38 is broken, use any other version >= 3.29". - " (Debian 5.0? aptitude remove libcgi-pm-perl)" - if $CGI::VERSION == 3.38; - - #use CGI::Carp qw(fatalsToBrowser); - use CGI::Cookie; - use List::Util qw( max min ); - use Data::Dumper; - use Date::Format; - use Time::Local; - use Time::HiRes; - use Time::Duration; - use DateTime; - use DateTime::Format::Strptime; - use FS::Misc::DateTime qw( parse_datetime ); - use Lingua::EN::Inflect qw(PL); - Lingua::EN::Inflect::classical names=>0; #Categorys - use Tie::IxHash; - use URI; - use URI::Escape; - use HTML::Entities; - use HTML::TreeBuilder; - use HTML::TableExtract qw(tree); - use HTML::FormatText; - use HTML::Defang; - use JSON; -# use XMLRPC::Transport::HTTP; -# use XMLRPC::Lite; # for XMLRPC::Serializer - use MIME::Base64; - use IO::Handle; - use IO::File; - use IO::Scalar; - #not actually using this yet anyway...# use IPC::Run3 0.036; - use Net::Whois::Raw qw(whois); - if ( $] < 5.006 ) { - eval "use Net::Whois::Raw 0.32 qw(whois)"; - die $@ if $@; - } - use Text::CSV_XS; - use Spreadsheet::WriteExcel; - use Spreadsheet::WriteExcel::Utility; - use Business::CreditCard 0.30; #for mask-aware cardtype() - use NetAddr::IP; - use Net::Ping; - use Net::Ping::External; - #if CPAN #7815 ever gets fixed# if ( $Net::Ping::External::VERSION <= 0.12 ) - { - no warnings 'redefine'; - eval 'sub Net::Ping::External::_ping_linux { - my %args = @_; - my $command = "ping -s $args{size} -c $args{count} -w $args{timeout} $args{host}"; - return Net::Ping::External::_ping_system($command, 0); - } - '; - die $@ if $@; - } - use String::Approx qw(amatch); - use Chart::LinesPoints; - use Chart::Mountain; - use Chart::Bars; - use Color::Scheme; - use HTML::Widgets::SelectLayers 0.07; #should go away in favor of - #selectlayers.html - use Locale::Country; - use Business::US::USPS::WebTools::AddressStandardization; - use LWP::UserAgent; - use Storable qw( nfreeze thaw ); - use FS; - use FS::UID qw( getotaker dbh datasrc driver_name ); - use FS::Record qw( qsearch qsearchs fields dbdef - str2time_sql str2time_sql_closing - ); - use FS::Conf; - use FS::CGI qw(header menubar table itable ntable idiot - eidiot myexit http_header); - use FS::UI::Web qw(svc_url); - use FS::UI::Web::small_custview qw(small_custview); - use FS::UI::bytecount; - use FS::Msgcat qw(gettext geterror); - use FS::Misc qw( send_email send_fax ocr_image - states_hash counties cities state_label - ); - use FS::Misc::eps2png qw( eps2png ); - use FS::Report::FCC_477; - use FS::Report::Table::Monthly; - use FS::TicketSystem; - use FS::Tron qw( tron_lint ); - - 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::Search qw(smart_search); - use FS::cust_main::Import; - use FS::cust_main_county; - use FS::cust_location; - use FS::cust_pay; - use FS::cust_pkg; - use FS::cust_pkg::Import; - use FS::part_pkg_taxclass; - use FS::cust_pkg_reason; - use FS::cust_refund; - use FS::cust_credit_refund; - use FS::cust_pay_refund; - use FS::cust_svc; - use FS::nas; - use FS::part_bill_event; - use FS::part_event; - use FS::part_event_condition; - use FS::part_pkg; - use FS::part_referral; - use FS::part_svc; - use FS::part_svc_router; - use FS::part_virtual_field; - use FS::pay_batch; - use FS::pkg_svc; - use FS::port; - use FS::queue qw(joblisting); - use FS::raddb; - use FS::session; - use FS::svc_acct; - use FS::svc_acct_pop qw(popselector); - use FS::acct_rt_transaction; - use FS::svc_domain; - use FS::svc_forward; - use FS::svc_www; - use FS::router; - use FS::addr_block; - use FS::svc_broadband; - use FS::svc_external; - use FS::type_pkgs; - use FS::part_export; - use FS::part_export_option; - use FS::export_svc; - use FS::export_device; - use FS::msgcat; - use FS::rate; - use FS::rate_region; - use FS::rate_prefix; - use FS::rate_detail; - use FS::usage_class; - use FS::payment_gateway; - use FS::agent_payment_gateway; - use FS::XMLRPC; - use FS::payby; - use FS::cdr; - use FS::cdr_batch; - use FS::inventory_class; - use FS::inventory_item; - use FS::pkg_category; - use FS::pkg_class; - use FS::access_user; - use FS::access_user_pref; - use FS::access_group; - use FS::access_usergroup; - use FS::access_groupagent; - use FS::access_right; - use FS::AccessRight; - use FS::svc_phone; - use FS::phone_device; - use FS::part_device; - use FS::reason_type; - use FS::reason; - use FS::cust_main_note; - use FS::tax_class; - use FS::cust_tax_location; - use FS::part_pkg_taxproduct; - use FS::part_pkg_taxoverride; - use FS::part_pkg_taxrate; - use FS::tax_rate; - use FS::part_pkg_report_option; - use FS::cust_attachment; - use FS::h_cust_pkg; - use FS::h_inventory_item; - use FS::h_svc_acct; - use FS::h_svc_broadband; - use FS::h_svc_domain; - #use FS::h_domain_record; - use FS::h_svc_external; - use FS::h_svc_forward; - use FS::h_svc_phone; - #use FS::h_phone_device; - use FS::h_svc_www; - use FS::cust_statement; - use FS::cust_class; - use FS::cust_category; - use FS::prospect_main; - use FS::contact; - use FS::svc_pbx; - use FS::discount; - use FS::cust_pkg_discount; - use FS::cust_bill_pkg_discount; - use FS::svc_mailinglist; - use FS::cgp_rule; - use FS::cgp_rule_condition; - use FS::cgp_rule_action; - use FS::bill_batch; - use FS::cust_bill_batch; - use FS::rate_time; - use FS::rate_time_interval; - use FS::msg_template; - use FS::part_tag; - use FS::acct_snarf; - use FS::part_pkg_discount; - use FS::svc_cert; - use FS::svc_dsl; - use FS::qual; - use FS::qual_option; - use FS::dsl_note; - use FS::part_pkg_vendor; - use FS::cust_note_class; - # Sammath Naur - - if ( $FS::Mason::addl_handler_use ) { - eval $FS::Mason::addl_handler_use; - die $@ if $@; - } - - if ( %%%RT_ENABLED%%% ) { - eval ' - use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" ); - use vars qw($Nobody $SystemUser); - use RT; - use RT::Util; - use RT::Tickets; - use RT::Transactions; - use RT::Users; - use RT::CurrentUser; - use RT::Templates; - use RT::Queues; - use RT::ScripActions; - use RT::ScripConditions; - use RT::Scrips; - use RT::Groups; - use RT::GroupMembers; - use RT::CustomFields; - use RT::CustomFieldValues; - use RT::ObjectCustomFieldValues; - - #blah. manually updated from RT::Interface::Web::Handler - use RT::Interface::Web; - use MIME::Entity; - use Text::Wrapper; - use Time::ParseDate; - use Time::HiRes; - use HTML::Scrubber; - - #blah. not even in RT::Interface::Web::Handler, just in - #html/NoAuth/css/dhandler and rt-test-dependencies. ask for it here - #to throw a real error instead of just a mysterious unstyled RT - use CSS::Squish 0.06; - - use RT::Interface::Web::Request; - - #nother undeclared web UI dep (for ticket links graph) - use IPC::Run::SafeHandles; - - #slow, unreliable, segfaults and is optional - #see rt/html/Ticket/Elements/ShowTransactionAttachments - #use Text::Quoted; - - #?#use File::Path qw( rmtree ); - #?#use File::Glob qw( bsd_glob ); - #?#use File::Spec::Unix; - - '; - die $@ if $@; - } - - *CGI::redirect = sub { - my $self = shift; - my $cookie = ''; - if ( $_[0] eq '-cookie' ) { #this isn't actually used at the moment - (my $x, $cookie) = (shift, shift); - $HTML::Mason::r->err_headers_out->add( 'Set-cookie' => $cookie ); - } - my $location = shift; - - use vars qw($m); - - # false laziness w/below - if ( defined(@DBIx::Profile::ISA) ) { - - if ( $FS::CurrentUser::CurrentUser->option('show_db_profile') ) { - - #profiling redirect - - my $page = - qq!Redirect to $location!. - '

    '.
    -            ( UNIVERSAL::can(dbh, 'sprintProfile')
    -                ? encode_entities(dbh->sprintProfile())
    -                : 'DBIx::Profile missing sprintProfile method;'.
    -                  'unpatched or too old?'                        ).
    -          #"\n\n". &sprintAutoProfile().  '
    '. - "\n\n". ''. - ''; - - - dbh->{'private_profile'} = {}; - return $page; - - } else { - - #clear db profile, but normal redirect - dbh->{'private_profile'} = {}; - $m->redirect($location); - ''; - - } - - } else { #normal redirect - - $m->redirect($location); - ''; - - } - - }; - - sub include { - use vars qw($m); - #carp #should just switch to <& &> syntax - $m->scomp(@_); - } - - sub errorpage { - use vars qw($m); - $m->comp('/elements/errorpage.html', @_); - } - - sub errorpage_popup { - use vars qw($m); - $m->comp('/elements/errorpage-popup.html', @_); - } - - sub redirect { - my( $location ) = @_; - use vars qw($m); - $m->clear_buffer; - #false laziness w/above - if ( defined(@DBIx::Profile::ISA) ) { - - if ( $FS::CurrentUser::CurrentUser->option('show_db_profile') ) { - - #profiling redirect - - $m->print( - qq!Redirect to $location!. - '

    '.
    -            ( UNIVERSAL::can(dbh, 'sprintProfile')
    -                ? encode_entities(dbh->sprintProfile())
    -                : 'DBIx::Profile missing sprintProfile method;'.
    -                  'unpatched or too old?'                        ).
    -          #"\n\n". &sprintAutoProfile().  '
    '. - "\n\n". ''. - '' - ); - - dbh->{'private_profile'} = {}; - - } else { - - #clear db profile, but normal redirect - dbh->{'private_profile'} = {}; - $m->redirect($location); - - } - - } else { #normal redirect - - $m->redirect($location); - - } - - } - -} # end package HTML::Mason::Commands; - -=head1 SUBROUTINE - -=over 4 - -=item mason_interps [ MODE [ OPTION => VALUE ... ] ] - -Returns a list consisting of two HTML::Mason::Interp objects, the first for -Freeside pages, and the second for RT pages. - -MODE can be 'apache' or 'standalone'. If not specified, defaults to 'apache'. - -Options and values can be passed following mode. Currently available options -are: - -I should be set to a scalar reference in standalone mode. - -=cut - -my %defang_opts = ( attribs_to_callback => ['src'], attribs_callback => sub { 1 }); - -sub mason_interps { - my $mode = shift || 'apache'; - my %opt = @_; - - #my $request_class = 'HTML::Mason::Request'. - #( $mode eq 'apache' ? '::ApacheHandler' : '' ); - my $request_class = 'FS::Mason::Request'; - - #not entirely sure it belongs here, but what the hey - if ( %%%RT_ENABLED%%% && $mode ne 'standalone' ) { - RT::LoadConfig(); - } - - # A hook supporting strange legacy ways people (well, SG) have added stuff on - - my @addl_comp_root = (); - my $addl_comp_root_file = '%%%FREESIDE_CONF%%%/addl_comp_root.pl'; - if ( -e $addl_comp_root_file ) { - warn "reading $addl_comp_root_file\n"; - my $text = slurp( $addl_comp_root_file ); - my @addl = eval $text; - if ( @addl && ! $@ ) { - @addl_comp_root = @addl; - } elsif ($@) { - warn "error parsing $addl_comp_root_file: $@\n"; - } - } - - my $fs_comp_root = - scalar(@addl_comp_root) - ? [ - [ 'freeside'=>'%%%FREESIDE_DOCUMENT_ROOT%%%' ], - @addl_comp_root, - ] - : '%%%FREESIDE_DOCUMENT_ROOT%%%'; - - my %interp = ( - request_class => $request_class, - data_dir => '%%%MASONDATA%%%', - error_mode => 'output', - error_format => 'html', - ignore_warnings_expr => '.', - ); - - $interp{out_method} = $opt{outbuf} if $mode eq 'standalone' && $opt{outbuf}; - - my $html_defang = new HTML::Defang (%defang_opts); - - my $js_string_sub = sub { - #${$_[0]} =~ s/(['\\\n])/'\\'.($1 eq "\n" ? 'n' : $1)/ge; - ${$_[0]} =~ s/(['\\])/\\$1/g; - ${$_[0]} =~ s/\r/\\r/g; - ${$_[0]} =~ s/\n/\\n/g; - ${$_[0]} = "'". ${$_[0]}. "'"; - }; - - my $fs_interp = new HTML::Mason::Interp ( - %interp, - comp_root => $fs_comp_root, - escape_flags => { 'js_string' => $js_string_sub, - 'defang' => sub { - ${$_[0]} = $html_defang->defang(${$_[0]}); - }, - }, - compiler => HTML::Mason::Compiler::ToObject->new( - allow_globals => [qw(%session)], - ), - ); - - my $rt_interp = new HTML::Mason::Interp ( - %interp, - comp_root => [ - [ 'rt' => '%%%FREESIDE_DOCUMENT_ROOT%%%/rt' ], - [ 'freeside' => '%%%FREESIDE_DOCUMENT_ROOT%%%' ], - ], - escape_flags => { 'h' => \&RT::Interface::Web::EscapeUTF8, - 'js_string' => $js_string_sub, - }, - compiler => HTML::Mason::Compiler::ToObject->new( - default_escape_flags => 'h', - allow_globals => [qw(%session)], - ), - ); - - ( $fs_interp, $rt_interp ); - -} - -=back - -=head1 BUGS - -Lurking in the darkness... - -=head1 SEE ALSO - -L, L, L - -=cut - -1; diff --git a/FS/FS/Mason/Request.pm b/FS/FS/Mason/Request.pm deleted file mode 100644 index 95c802796..000000000 --- a/FS/FS/Mason/Request.pm +++ /dev/null @@ -1,87 +0,0 @@ -package FS::Mason::Request; - -use strict; -use warnings; -use vars qw( $FSURL $QUERY_STRING ); -use base 'HTML::Mason::Request'; - -$FSURL = 'http://Set/FS_Mason_Request_FSURL/in_standalone_mode/'; -$QUERY_STRING = ''; - -sub new { - my $class = shift; - - my $superclass = $HTML::Mason::ApacheHandler::VERSION ? - 'HTML::Mason::Request::ApacheHandler' : - $HTML::Mason::CGIHandler::VERSION ? - 'HTML::Mason::Request::CGI' : - 'HTML::Mason::Request'; - - $class->alter_superclass( $superclass ); - - #huh... shouldn't alter_superclass take care of this for us? - __PACKAGE__->valid_params( %{ $superclass->valid_params() } ); - - my %opt = @_; - my $mode = $superclass =~ /Apache/i ? 'apache' : 'standalone'; - freeside_setup($opt{'comp'}, $mode); - - $class->SUPER::new(@_); - -} - -#override alter_superclass ala RT::Interface::Web::Request ?? -# for Mason 1.39 vs. Perl 5.10.0 - -sub freeside_setup { - - my( $filename, $mode ) = @_; - - if ( $filename =~ qr(/REST/\d+\.\d+/NoAuth/) ) { - - package HTML::Mason::Commands; #? - use FS::UID qw( adminsuidsetup ); - - #need to log somebody in for the mail gw - - ##old installs w/fs_selfs or selfserv?? - #&adminsuidsetup('fs_selfservice'); - - &adminsuidsetup('fs_queue'); - - } else { - - package HTML::Mason::Commands; - use vars qw( $cgi $p $fsurl ); - use FS::UID qw( cgisuidsetup ); - use FS::CGI qw( popurl rooturl ); - - if ( $mode eq 'apache' ) { - $cgi = new CGI; - &cgisuidsetup($cgi); - #&cgisuidsetup($r); - $fsurl = rooturl(); - $p = popurl(2); - } elsif ( $mode eq 'standalone' ) { - $cgi = new CGI $FS::Mason::Request::QUERY_STRING; #better keep setting - #if you set it once - $FS::UID::cgi = $cgi; - $fsurl = $FS::Mason::Request::FSURL; #kludgy, but what the hell - $p = popurl(2, "$fsurl$filename"); - } else { - die "unknown mode $mode"; - } - - } - -} - -sub callback { - RT::Interface::Web::Request::callback(@_); -} - -sub request_path { - RT::Interface::Web::Request::request_path(@_); -} - -1; diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm deleted file mode 100644 index fe8ac6082..000000000 --- a/FS/FS/Misc.pm +++ /dev/null @@ -1,904 +0,0 @@ -package FS::Misc; - -use strict; -use vars qw ( @ISA @EXPORT_OK $DEBUG ); -use Exporter; -use Carp; -use Data::Dumper; -use IPC::Run qw( run timeout ); # for _pslatex -use IPC::Run3; # for do_print... should just use IPC::Run i guess -use File::Temp; -use Tie::IxHash; -#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 generate_email send_fax - states_hash counties cities state_label - card_types - pkg_freqs - generate_ps generate_pdf do_print - csv_from_fixed - ocr_image - ); - -$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 eliminate code duplication. - -=head1 SUBROUTINES - -=over 4 - -=item send_email OPTION => VALUE ... - -Options: - -=over 4 - -=item from - -(required) - -=item to - -(required) comma-separated scalar or arrayref of recipients - -=item subject - -(required) - -=item content-type - -(optional) MIME type for the body - -=item body - -(required unless I is true) arrayref of body text lines - -=item mimeparts - -(optional, but required if I is true) arrayref of MIME::Entity->build PARAMHASH refs or MIME::Entity objects. These will be passed as arguments to MIME::Entity->attach(). - -=item nobody - -(optional) when set true, send_email will ignore the I option and simply construct a message with the given I. In this case, -I, if specified, overrides the default "multipart/mixed" for the outermost MIME container. - -=item content-encoding - -(optional) when using nobody, optional top-level MIME -encoding which, if specified, overrides the default "7bit". - -=item type - -(optional) type parameter for multipart/related messages - -=back - -=cut - -use vars qw( $conf ); -use Date::Format; -use MIME::Entity; -use Email::Sender::Simple qw(sendmail); -use Email::Sender::Transport::SMTP; -use Email::Sender::Transport::SMTP::TLS; -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" - } - - my @to = ref($options{to}) ? @{ $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 (and saying HELO) @example.com'; - $domain = 'example.com'; - } - my $message_id = join('.', rand()*(2**32), $$, time). "\@$domain"; - - my $message = MIME::Entity->build( - 'From' => $options{'from'}, - 'To' => join(', ', @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!"; - } - - } - - #send the email - - my %smtp_opt = ( 'host' => $conf->config('smtpmachine'), - 'helo' => $domain, - ); - - my($port, $enc) = split('-', ($conf->config('smtp-encryption') || '25') ); - $smtp_opt{'port'} = $port; - - my $transport; - if ( defined($enc) && $enc eq 'starttls' ) { - $smtp_opt{$_} = $conf->config("smtp-$_") for qw(username password); - $transport = Email::Sender::Transport::SMTP::TLS->new( %smtp_opt ); - } else { - if ( $conf->exists('smtp-username') && $conf->exists('smtp-password') ) { - $smtp_opt{"sasl_$_"} = $conf->config("smtp-$_") for qw(username password); - } - $smtp_opt{'ssl'} = 1 if defined($enc) && $enc eq 'tls'; - $transport = Email::Sender::Transport::SMTP->new( %smtp_opt ); - } - - push @to, $options{bcc} if defined($options{bcc}); - local $@; # just in case - eval { sendmail($message, { transport => $transport, - from => $options{from}, - to => \@to }) }; - - if(ref($@) and $@->isa('Email::Sender::Failure')) { - return ($@->code ? $@->code.' ' : '').$@->message - } - else { - return $@; - } -} - -=item generate_email OPTION => VALUE ... - -Options: - -=over 4 - -=item from - -Sender address, required - -=item to - -Recipient address, required - -=item bcc - -Blind copy address, optional - -=item subject - -email subject, required - -=item html_body - -Email body (HTML alternative). Arrayref of lines, or scalar. - -Will be placed inside an HTML tag. - -=item text_body - -Email body (Text alternative). Arrayref of lines, or scalar. - -=back - -Constructs a multipart message from text_body and html_body. - -=cut - -#false laziness w/FS::cust_bill::generate_email - -use MIME::Entity; -use HTML::Entities; - -sub generate_email { - my %args = @_; - - my $me = '[FS::Misc::generate_email]'; - - my %return = ( - 'from' => $args{'from'}, - 'to' => $args{'to'}, - 'bcc' => $args{'bcc'}, - 'subject' => $args{'subject'}, - ); - - #if (ref($args{'to'}) eq 'ARRAY') { - # $return{'to'} = $args{'to'}; - #} else { - # $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ } - # $self->cust_main->invoicing_list - # ]; - #} - - 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 ( ref($args{'text_body'}) eq 'ARRAY' ) { - $data = $args{'text_body'}; - } else { - $data = [ split(/\n/, $args{'text_body'}) ]; - } - - $alternative->attach( - 'Type' => 'text/plain', - #'Encoding' => 'quoted-printable', - 'Encoding' => '7bit', - 'Data' => $data, - 'Disposition' => 'inline', - ); - - my @html_data; - if ( ref($args{'html_body'}) eq 'ARRAY' ) { - @html_data = @{ $args{'html_body'} }; - } else { - @html_data = split(/\n/, $args{'html_body'}); - } - - $alternative->attach( - 'Type' => 'text/html', - 'Encoding' => 'quoted-printable', - 'Data' => [ '', - ' ', - ' ', - ' '. encode_entities($return{'subject'}), - ' ', - ' ', - ' ', - @html_data, - ' ', - '', - ], - 'Disposition' => 'inline', - #'Filename' => 'invoice.pdf', - ); - - #no other attachment: - # multipart/related - # multipart/alternative - # text/plain - # text/html - - $return{'content-type'} = 'multipart/related'; - $return{'mimeparts'} = [ $alternative ]; - $return{'type'} = 'multipart/alternative'; #Content-Type of first part... - #$return{'disposition'} = 'inline'; - - %return; - -} - -=item process_send_email OPTION => VALUE ... - -Takes arguments as per generate_email() and sends the message. This -will die on any error and can be used in the job queue. - -=cut - -sub process_send_email { - my %message = @_; - my $error = send_email(generate_email(%message)); - die "$error\n" if $error; - ''; -} - -=item send_fax OPTION => VALUE ... - -Options: - -I - (required) 10-digit phone number w/ area code - -I - (required) Array ref containing PostScript or TIFF Class F document - --or- - -I - (required) Filename of PostScript TIFF Class F document - -...any other options will be passed to L - - -=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 ) = @_; - - map { $_ } #return num_counties($state, $country) unless wantarray; - sort map { s/[\n\r]//g; $_; } - map { $_->county } - qsearch({ - 'select' => 'DISTINCT county', - 'table' => 'cust_main_county', - 'hashref' => { 'state' => $state, - 'country' => $country, - }, - }); -} - -=item cities COUNTY STATE COUNTRY - -Returns a list of cities for this county, state and country. - -=cut - -sub cities { - my( $county, $state, $country ) = @_; - - map { $_ } #return num_cities($county, $state, $country) unless wantarray; - sort map { s/[\n\r]//g; $_; } - map { $_->city } - qsearch({ - 'select' => 'DISTINCT city', - 'table' => 'cust_main_county', - 'hashref' => { 'county' => $county, - '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). - -=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 pkg_freqs - -Returns a hash reference of allowed package billing frequencies. - -=cut - -sub pkg_freqs { - 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 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); - - _pslatex($file); - - 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 () { - $ps .= $_; - } - - close POSTSCRIPT; - - if ( $conf->exists('lpr-postscript_suffix') ) { - my $suffix = $conf->config('lpr-postscript_suffix'); - $ps .= eval qq("$suffix"); - } - - return $ps; - -} - -=item generate_pdf FILENAME - -Returns an PDF 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_pdf { - my $file = shift; - - 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. - - _pslatex($file); - - my $sfile = shell_quote $file; - - #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"); - - my $pdf = ''; - while () { - $pdf .= $_; - } - - close PDF; - - return $pdf; - -} - -sub _pslatex { - my $file = shift; - - #my $sfile = shell_quote $file; - - my @cmd = ( - 'latex', - '-interaction=batchmode', - '\AtBeginDocument{\RequirePackage{pslatex}}', - '\def\PSLATEXTMP{\futurelet\PSLATEXTMP\PSLATEXTMPB}', - '\def\PSLATEXTMPB{\ifx\PSLATEXTMP\nonstopmode\else\input\fi}', - '\PSLATEXTMP', - "$file.tex" - ); - - my $timeout = 30; #? should be more than enough - - for ( 1, 2 ) { - - local($SIG{CHLD}) = sub {}; - run( \@cmd, '>'=>'/dev/null', '2>'=>'/dev/null', timeout($timeout) ) - or die "pslatex $file.tex failed; see $file.log for details?\n"; - - } - -} - -=item print ARRAYREF - -Sends the lines in ARRAYREF to the printer. - -=cut - -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"; - } - -} - -=item csv_from_fixed, FILEREF COUNTREF, [ LENGTH_LISTREF, [ CALLBACKS_LISTREF ] ] - -Converts the filehandle referenced by FILEREF from fixed length record -lines to a CSV file according to the lengths specified in LENGTH_LISTREF. -The CALLBACKS_LISTREF refers to a correpsonding list of coderefs. Each -should return the value to be substituted in place of its single argument. - -Returns false on success or an error if one occurs. - -=cut - -sub csv_from_fixed { - my( $fhref, $countref, $lengths, $callbacks) = @_; - - eval { require Text::CSV_XS; }; - return $@ if $@; - - my $ofh = $$fhref; - my $unpacker = new Text::CSV_XS; - my $total = 0; - my $template = join('', map {$total += $_; "A$_"} @$lengths) if $lengths; - - my $dir = "%%%FREESIDE_CACHE%%%/cache.$FS::UID::datasrc"; - my $fh = new File::Temp( TEMPLATE => "FILE.csv.XXXXXXXX", - DIR => $dir, - UNLINK => 0, - ) or return "can't open temp file: $!\n" - if $template; - - while ( defined(my $line=<$ofh>) ) { - $$countref++; - if ( $template ) { - my $column = 0; - - chomp $line; - return "unexpected input at line $$countref: $line". - " -- expected $total but received ". length($line) - unless length($line) == $total; - - $unpacker->combine( map { my $i = $column++; - defined( $callbacks->[$i] ) - ? &{ $callbacks->[$i] }( $_ ) - : $_ - } unpack( $template, $line ) - ) - or return "invalid data for CSV: ". $unpacker->error_input; - - print $fh $unpacker->string(), "\n" - or return "can't write temp file: $!\n"; - } - } - - if ( $template ) { close $$fhref; $$fhref = $fh } - - seek $$fhref, 0, 0; - ''; -} - -=item ocr_image IMAGE_SCALAR - -Runs OCR on the provided image data and returns a list of text lines. - -=cut - -sub ocr_image { - my $logo_data = shift; - - #XXX use conf dir location from Makefile - my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc; - my $fh = new File::Temp( - TEMPLATE => 'bizcard.XXXXXXXX', - SUFFIX => '.png', #XXX assuming, but should handle jpg, gif, etc. too - DIR => $dir, - UNLINK => 0, - ) or die "can't open temp file: $!\n"; - - my $filename = $fh->filename; - - print $fh $logo_data; - close $fh; - - run( [qw(ocroscript recognize), $filename], '>'=>"$filename.hocr" ) - or die "ocroscript recognize failed\n"; - - run( [qw(ocroscript hocr-to-text), "$filename.hocr"], '>pipe'=>\*OUT ) - or die "ocroscript hocr-to-text failed\n"; - - my @lines = split(/\n/, ); - - foreach (@lines) { s/\.c0m\s*$/.com/; } - - @lines; -} - -=back - -=head1 BUGS - -This package exists. - -=head1 SEE ALSO - -L, L, L, the base documentation. - -L - -=cut - -1; diff --git a/FS/FS/Misc/DateTime.pm b/FS/FS/Misc/DateTime.pm deleted file mode 100644 index a32c15aea..000000000 --- a/FS/FS/Misc/DateTime.pm +++ /dev/null @@ -1,64 +0,0 @@ -package FS::Misc::DateTime; - -use base qw( Exporter ); -use vars qw( @EXPORT_OK ); -use Carp; -use Date::Parse; -use DateTime::Format::Natural; -use FS::Conf; - -@EXPORT_OK = qw( parse_datetime ); - -=head1 NAME - -FS::Misc::DateTime - Date and time subroutines - -=head1 SYNOPSIS - -use FS::Misc::DateTime qw( parse_datetime ); - -=head1 SUBROUTINES - -=over 4 - -=item parse_datetime STRING - -Parses a date (and possibly time) from the supplied string and returns -the date as an integer UNIX timestamp. - -=cut - -sub parse_datetime { - my $string = shift; - return '' unless $string =~ /\S/; - - my $conf = new FS::Conf; - my $format = $conf->config('date_format') || '%m/%d/%Y'; - - if ( $format eq '%d/%m/%Y' ) { # =~ /\%d.*\%m/ ) { - #$format =~ s/\%//g; - my $parser = DateTime::Format::Natural->new( 'time_zone' => 'local', - #'format'=>'d/m/y',#lc($format) - ); - $dt = $parser->parse_datetime($string); - if ( $parser->success ) { - return $dt->epoch; - } else { - #carp "WARNING: can't parse date: ". $parser->error; - #return ''; - #huh, very common, we still need the "partially" (fully enough for our purposes) parsed date. - $dt->epoch; - } - } else { - return str2time($string); - } - -} - -=back - -=head1 BUGS - -=cut - -1; diff --git a/FS/FS/Misc/eps2png.pm b/FS/FS/Misc/eps2png.pm deleted file mode 100644 index aa8e5729a..000000000 --- a/FS/FS/Misc/eps2png.pm +++ /dev/null @@ -1,278 +0,0 @@ -package FS::Misc::eps2png; - -#based on eps2png by Johan Vromans -#Copyright 1994,2008 by Johan Vromans. -#This program is free software; you can redistribute it and/or -#modify it under the terms of the Perl Artistic License or the -#GNU General Public License as published by the Free Software -#Foundation; either version 2 of the License, or (at your option) any -#later version. - -use strict; -use vars qw( @ISA @EXPORT_OK ); -use Exporter; -use File::Temp; -use File::Slurp qw( slurp ); -#use FS::UID; - -@ISA = qw( Exporter ); -@EXPORT_OK = qw( eps2png ); - -################ Program parameters ################ - -# Some GhostScript programs can produce GIF directly. -# If not, we need the PBM package for the conversion. -# NOTE: This will be changed upon install. -my $use_pbm = 0; - -my $res = 82; # default resolution -my $scale = 1; # default scaling -my $mono = 0; # produce BW images if non-zero -my $format; # output format -my $gs_format; # GS output type -my $output; # output, defaults to STDOUT -my $antialias = 4; # antialiasing -my $DEF_width; # desired widht -my $DEF_height; # desired height -#my $DEF_width = 90; # desired widht -#my $DEF_height = 36; # desired height - -my ($verbose,$trace,$test,$debug) = (0,0,0,0); -#handle_options (); -set_out_type ('png'); # unless defined $format; -warn "Producing $format ($gs_format) image.\n" if $verbose; - -$trace |= $test | $debug; -$verbose |= $trace; - -################ Presets ################ - -################ The Process ################ - -my $err = 0; - -sub eps2png { - my( $eps, %options ) = @_; #well, no options yet - - my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc; - my $eps_file = new File::Temp( TEMPLATE => 'image.XXXXXXXX', - DIR => $dir, - SUFFIX => '.eps', - #UNLINK => 0, - ) or die "can't open temp file: $!\n"; - print $eps_file $eps; - close $eps_file; - - my @eps = split(/\r?\n/, $eps); - - warn "converting eps (". length($eps). " bytes, ". scalar(@eps). " lines)\n" - if $verbose; - - my $line = shift @eps; #; - unless ( $eps =~ /^%!PS-Adobe.*EPSF-/ ) { - warn "not EPS file (no %!PS-Adobe header)\n"; - return; #empty png file? - } - - my $ps = ""; # PostScript input data - my $xscale; - my $yscale; - my $gotbb; - - # Prevent derived values from propagating. - my $width = $DEF_width; - my $height = $DEF_height; - - while ( @eps ) { - - $line = shift(@eps)."\n"; - - # Search for BoundingBox. - if ( $line =~ /^%%BoundingBox:\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/i ) { - $gotbb++; - warn "$eps_file: x0=$1, y0=$2, w=", $3-$1, ", h=", $4-$2 - if $verbose; - - if ( defined $width ) { - $res = 72; - $xscale = $width / ($3 - $1); - if ( defined $height ) { - $yscale = $height / ($4 - $2); - } - else { - $yscale = $xscale; - $height = ($4 - $2) * $yscale; - } - } - elsif ( defined $height ) { - $res = 72; - $yscale = $height / ($4 - $2); - if ( defined $width ) { - $xscale = $width / ($3 - $1); - } - else { - $xscale = $yscale; - $width = ($3 - $1) * $xscale; - } - } - unless ( defined $xscale ) { - $xscale = $yscale = $scale; - # Calculate actual width. - $width = $3 - $1; - $height = $4 - $2; - # Normal PostScript resolution is 72. - $width *= $res/72 * $xscale; - $height *= $res/72 * $yscale; - # Round up. - $width = int ($width + 0.5) + 1; - $height = int ($height + 0.5) + 1; - } - warn ", width=$width, height=$height\n" if $verbose; - - # Scale. - $ps .= "$xscale $yscale scale\n" - if $xscale != 1 || $yscale != 1; - - # Create PostScript code to translate coordinates. - $ps .= (0-$1) . " " . (0-$2) . " translate\n" - unless $1 == 0 && $2 == 0; - - # Include the image, show and quit. - $ps .= "($eps_file) run\n". - "showpage\n". - "quit\n"; - - last; - } - elsif ( $line =~ /^%%EndComments/i ) { - last; - } - } - - unless ( $gotbb ) { - warn "No bounding box in $eps_file\n"; - return; - } - - #it would be better to ask gs to spit out files on stdout, but c'est la vie - - #my $out_file; # output file - #my $pbm_file; # temporary file for PBM conversion - - my $out_file = new File::Temp( TEMPLATE => 'image.XXXXXXXX', - DIR => $dir, - SUFFIX => '.png', - #UNLINK => 0, - ) or die "can't open temp file: $!\n"; - - my $pbm_file = new File::Temp( TEMPLATE => 'image.XXXXXXXX', - DIR => $dir, - SUFFIX => '.pbm', - #UNLINK => 0, - ) or die "can't open temp file: $!\n"; - - # Note the temporary PBM file is created where the output file is - # located, since that will guarantee accessibility (and a valid - # filename). - warn "Creating $out_file\n" if $verbose; - - my $gs0 = "gs -q -dNOPAUSE -r$res -g${width}x$height"; - my $gs1 = "-"; - $gs0 .= " -dTextAlphaBits=$antialias -dGraphicsAlphaBits=$antialias" - if $antialias; - if ( $format eq 'png' ) { - mysystem ("$gs0 -sDEVICE=". ($mono ? "pngmono" : $gs_format). - " -sOutputFile=$out_file $gs1", $ps); - } - elsif ( $format eq 'jpg' ) { - mysystem ("$gs0 -sDEVICE=". ($mono ? "jpeggray" : $gs_format). - " -sOutputFile=$out_file $gs1", $ps); - } - elsif ( $format eq 'gif' ) { - if ( $use_pbm ) { - # Convert to PPM and use some of the PBM converters. - mysystem ("$gs0 -sDEVICE=". ($mono ? "pbm" : "ppm"). - " -sOutputFile=$pbm_file $gs1", $ps); - # mysystem ("pnmcrop $pbm_file | ppmtogif > $out_file"); - mysystem ("ppmtogif $pbm_file > $out_file"); - unlink ($pbm_file); - } - else { - # GhostScript has GIF drivers built-in. - mysystem ("$gs0 -sDEVICE=". ($mono ? "gifmono" : "gif8"). - " -sOutputFile=$out_file $gs1", $ps); - } - } - else { - warn "ASSERT ERROR: Unhandled output type: $format\n"; - exit (1); - } - -# unless ( -s $out_file ) { -# warn "Problem creating $out_file for $eps_file\n"; -# $err++; -# } - - slurp($out_file); - -} - -exit 1 if $err; - -################ Subroutines ################ - -sub mysystem { - my ($cmd, $data) = @_; - warn "+ $cmd\n" if $trace; - if ( $data ) { - if ( $trace ) { - my $dp = ">> " . $data; - $dp =~ s/\n(.)/\n>> $1/g; - warn "$dp"; - } - open (CMD, "|$cmd") or die ("$cmd: $!\n"); - print CMD $data; - close CMD or die ("$cmd close: $!\n"); - } - else { - system ($cmd); - } -} - -sub set_out_type { - my ($opt) = lc (shift (@_)); - if ( $opt =~ /^png(mono|gray|16|256|16m|alpha)?$/ ) { - $format = 'png'; - $gs_format = $format.(defined $1 ? $1 : '16m'); - } - elsif ( $opt =~ /^gif(mono)?$/ ) { - $format = 'gif'; - $gs_format = $format.(defined $1 ? $1 : ''); - } - elsif ( $opt =~ /^(jpg|jpeg)(gray)?$/ ) { - $format = 'jpg'; - $gs_format = 'jpeg'.(defined $2 ? $2 : ''); - } - else { - warn "ASSERT ERROR: Invalid value to set_out_type: $opt\n"; - exit (1); - } -} - -# 'antialias|aa=i' => \$antialias, -# 'noantialias|noaa' => sub { $antialias = 0 }, -# 'scale=f' => \$scale, -# 'width=i' => \$width, -# 'height=i' => \$height, -# 'resolution=i' => \$res, - -# die ("Antialias value must be 0, 1, 2, 4, or 8\n") - -# -width XXX desired with -# -height XXX desired height -# -resolution XXX resolution (default = $res) -# -scale XXX scaling factor -# -antialias XX antialias factor (must be 0, 1, 2, 4 or 8; default: 4) -# -noantialias no antialiasing (same as -antialias 0) - -1; diff --git a/FS/FS/Misc/prune.pm b/FS/FS/Misc/prune.pm deleted file mode 100644 index 3f0c79d00..000000000 --- a/FS/FS/Misc/prune.pm +++ /dev/null @@ -1,131 +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(); - -=head1 SUBROUTINES - -=over 4 - -=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, L, and L). -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 = < { 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 70933b238..000000000 --- a/FS/FS/Msgcat.pm +++ /dev/null @@ -1,100 +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; #wtf? causes dependency loops too. -use FS::msgcat; - -@ISA = qw(Exporter); -@EXPORT_OK = qw( gettext geterror ); - -FS::UID->install_callback( sub { - eval "use FS::Conf;"; - die $@ if $@; - $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 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, L, 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 c37dd7855..000000000 --- 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 2e2612e35..000000000 --- a/FS/FS/Record.pm +++ /dev/null @@ -1,3157 +0,0 @@ -package FS::Record; - -use strict; -use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG - $conf $conf_encryption $me - %virtual_fields_cache - $nowarn_identical $nowarn_classload - $no_update_diff $no_check_foreign - ); -use Exporter; -use Carp qw(carp cluck croak confess); -use Scalar::Util qw( blessed ); -use File::CounterFile; -use Locale::Country; -use Text::CSV_XS; -use File::Slurp qw( slurp ); -use DBI qw(:sql_types); -use DBIx::DBSchema 0.38; -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; #dependency loop bs, in install_callback below instead - -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 str2time_sql_closing regexp_sql not_regexp_sql ); - -$DEBUG = 0; -$me = '[FS::Record]'; - -$nowarn_identical = 0; -$nowarn_classload = 0; -$no_update_diff = 0; -$no_check_foreign = 0; - -my $rsa_module; -my $rsa_loaded; -my $rsa_encrypt; -my $rsa_decrypt; - -$conf = ''; -$conf_encryption = ''; -FS::UID->install_callback( sub { - eval "use FS::Conf;"; - die $@ if $@; - $conf = FS::Conf->new; - $conf_encryption = $conf->exists('encryption'); - $File::CounterFile::DEFAULT_DIR = $conf->base_dir . "/counters.". datasrc; - if ( driver_name eq 'Pg' ) { - eval "use DBD::Pg ':pg_types'"; - die $@ if $@; - } else { - eval "sub PG_BYTEA { die 'guru meditation #9: calling PG_BYTEA when not running Pg?'; }"; - } -} ); - -=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 -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'} - unless $nowarn_classload; - } - - $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: - - @records = qsearch( { - 'table' => 'table_name', - 'hashref' => { 'field' => 'value' - 'field' => { 'op' => '<', - 'value' => '420', - }, - }, - - #these are optional... - 'select' => '*', - 'extra_sql' => 'AND field = ? AND intfield = ?', - 'extra_param' => [ 'value', [ 5, 'int' ] ], - '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' } ); - -Also possible is an experimental LISTREF of PARAMS_HASHREFs for a UNION of -the individual PARAMS_HASHREF queries - -###oops, argh, FS::Record::new only lets us create database fields. -#Normal behaviour if SELECT is not specified is `*', as in -#C
  • '; - foreach my $part_tag ( @part_tag ) { - $html .= ''; - } - $html .= '
    '. - 'tagcolor) - ? 'STYLE="background-color:#'.$part_tag->tagcolor.'"' - : '' - ). - '>'. - encode_entities($part_tag->tagname.': '. $part_tag->tagdesc). - ''. - '
    '; - } - - $html .= - ntable('#e8e8e8'). ''. ntable("#cccccc",2). - 'Billing
    Address'. - $cust_main->getfield('last'). ', '. $cust_main->first. '
    '; - - $html .= $cust_main->company. '
    ' if $cust_main->company; - $html .= $cust_main->address1. '
    '; - $html .= $cust_main->address2. '
    ' if $cust_main->address2; - $html .= $cust_main->city. ', '. $cust_main->state. ' '. $cust_main->zip. '
    '; - $html .= $cust_main->country. '
    ' - if $cust_main->country && $cust_main->country ne $countrydefault; - - $html .= ''; - if ( $cust_main->daytime && $cust_main->night ) { - $html .= ( FS::Msgcat::_gettext('daytime') || 'Day' ). - ' '. $cust_main->daytime. - '
    '. ( 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 .= '
    Fax '. $cust_main->fax; - } - - $html .= ''; - - if ( defined $cust_main->dbdef_table->column('ship_last') ) { - - my $pre = $cust_main->ship_last ? 'ship_' : ''; - - $html .= ''. ntable("#cccccc",2). - 'Service
    Address'. - $cust_main->get("${pre}last"). ', '. - $cust_main->get("${pre}first"). '
    '; - $html .= $cust_main->get("${pre}company"). '
    ' - if $cust_main->get("${pre}company"); - $html .= $cust_main->get("${pre}address1"). '
    '; - $html .= $cust_main->get("${pre}address2"). '
    ' - if $cust_main->get("${pre}address2"); - $html .= $cust_main->get("${pre}city"). ', '. - $cust_main->get("${pre}state"). ' '. - $cust_main->get("${pre}zip"). '
    '; - $html .= $cust_main->get("${pre}country"). '
    ' - if $cust_main->get("${pre}country") - && $cust_main->get("${pre}country") ne $countrydefault; - - $html .= ''; - - 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"). - '
    '. ( 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 .= '
    Fax '. $cust_main->get("${pre}fax"); - } - - $html .= ''; - } - - $html .= ''; - - $html .= '
    Balance: $'. $cust_main->balance. '
    ' - unless $nobalance; - - # last payment might be good here too? - - $html; -} - -#bah. don't want to pull in all of FS::CGI, that's the whole problem in the -#first place -sub ntable { - my $col = shift; - my $cellspacing = shift || 0; - if ( $col ) { - qq!!; - } else { - '
    '; - } - -} - -1; - diff --git a/FS/FS/UI/bytecount.pm b/FS/FS/UI/bytecount.pm deleted file mode 100644 index 7e78bf501..000000000 --- a/FS/FS/UI/bytecount.pm +++ /dev/null @@ -1,101 +0,0 @@ -package FS::UI::bytecount; - -use strict; -use vars qw($DEBUG $me @ISA @EXPORT_OK); -use Exporter; -use FS::Conf; -use Number::Format 1.50; - -@ISA = qw( Exporter ); - -@EXPORT_OK = qw( bytecount_unexact parse_bytecount display_bytecount ); - -$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/1024)) - if ($bc < 1048576); - return(sprintf("%.2f Mbytes", $bc/1048576)) - if ($bc < 1073741824); - return(sprintf("%.2f Gbytes", $bc/1073741824)); -} - -=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 - -=cut - -1; - diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm deleted file mode 100644 index e042c05b1..000000000 --- a/FS/FS/UID.pm +++ /dev/null @@ -1,405 +0,0 @@ -package FS::UID; - -use strict; -use vars qw( - @ISA @EXPORT_OK $DEBUG $me $cgi $freeside_uid $user $conf_dir $cache_dir - $secrets $datasrc $db_user $db_pass $schema $dbh $driver_name - $AutoCommit %callback @callback $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%%%"; -$cache_dir = "%%%FREESIDE_CACHE%%%"; - -$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 (\$>=$>, \$<=$<)\n" 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 ( ! $FS::Schema::setup_hack && 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 $FS::Schema::setup_hack; - } - - 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 { - my $handle = DBI->connect( getsecrets(@_), { 'AutoCommit' => 0, - 'ChopBlanks' => 1, - 'ShowErrorStatement' => 1, - } - ) - or die "DBI->connect error: $DBI::errstr\n"; - - if ( $schema ) { - use DBIx::DBSchema::_util qw(_load_driver ); #quelle hack - my $driver = _load_driver($handle); - if ( $driver =~ /^Pg/ ) { - no warnings 'redefine'; - eval "sub DBIx::DBSchema::DBD::${driver}::default_db_schema {'$schema'}"; - die $@ if $@; - } - } - - $handle; -} - -=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) or Apache (see L) -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) 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) or an Apache object (see L). 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 unless $>; #huh. mpm-itk hack - ( $> == $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, $schema) = - 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, L, L, 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 ba4a085b4..000000000 --- a/FS/FS/Upgrade.pm +++ /dev/null @@ -1,378 +0,0 @@ -package FS::Upgrade; - -use strict; -use vars qw( @ISA @EXPORT_OK $DEBUG ); -use Exporter; -use Tie::IxHash; -use FS::UID qw( dbh driver_name ); -use FS::Conf; -use FS::Record qw(qsearchs str2time_sql); - -use FS::svc_domain; -$FS::svc_domain::whois_hack = 1; - -@ISA = qw( Exporter ); -@EXPORT_OK = qw( upgrade_schema upgrade_config upgrade upgrade_sqlradius ); - -$DEBUG = 1; - -=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 upgrade_config - -=cut - -#config upgrades -sub upgrade_config { - my %opt = @_; - - my $conf = new FS::Conf; - - $conf->touch('payment_receipt') - if $conf->exists('payment_receipt_email') - || $conf->config('payment_receipt_msgnum'); - -} - -=item upgrade - -=cut - -sub upgrade { - my %opt = @_; - - my $data = upgrade_data(%opt); - - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - local $FS::UID::AutoCommit = 0; - - foreach my $table ( keys %$data ) { - - my $class = "FS::$table"; - eval "use $class;"; - die $@ if $@; - - if ( $class->can('_upgrade_data') ) { - warn "Upgrading $table...\n"; - - my $start = time; - - $class->_upgrade_data(%opt); - - if ( $oldAutoCommit ) { - warn " committing\n"; - dbh->commit or die dbh->errstr; - } - - #warn "\e[1K\rUpgrading $table... done in ". (time-$start). " seconds\n"; - warn " done in ". (time-$start). " seconds\n"; - - } 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; -# } - - } - -} - -=item upgrade_data - -=cut - -sub upgrade_data { - my %opt = @_; - - tie my %hash, 'Tie::IxHash', - - #cust_main (remove paycvv from history) - 'cust_main' => [], - - #msgcat - 'msgcat' => [], - - #reason type and reasons - 'reason_type' => [], - 'cust_pkg_reason' => [], - - #need part_pkg before cust_credit... - 'part_pkg' => [], - - #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' => [], - - #remove bad pending records - 'cust_pay_pending' => [], - - #replace invnum and pkgnum with billpkgnum - 'cust_bill_pkg_detail' => [], - - #usage_classes if we have none - 'usage_class' => [], - - #phone_type if we have none - 'phone_type' => [], - - #fixup access rights - 'access_right' => [], - - #change recur_flat and enable_prorate - 'part_pkg_option' => [], - - #add weights to pkg_category - 'pkg_category' => [], - - #cdrbatch fixes - 'cdr' => [], - - #otaker->usernum - 'cust_attachment' => [], - #'cust_credit' => [], - #'cust_main' => [], - 'cust_main_note' => [], - #'cust_pay' => [], - 'cust_pay_void' => [], - 'cust_pkg' => [], - #'cust_pkg_reason' => [], - 'cust_pkg_discount' => [], - 'cust_refund' => [], - 'banned_pay' => [], - - #default namespace - 'payment_gateway' => [], - - #migrate to templates - 'msg_template' => [], - - #return unprovisioned numbers to availability - 'phone_avail' => [], - - #insert scripcondition - 'TicketSystem' => [], - - ; - - \%hash; - -} - -=item upgrade_schema - -=cut - -sub upgrade_schema { - my %opt = @_; - - my $data = upgrade_schema_data(%opt); - - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - local $FS::UID::AutoCommit = 0; - - foreach my $table ( keys %$data ) { - - my $class = "FS::$table"; - eval "use $class;"; - die $@ if $@; - - if ( $class->can('_upgrade_schema') ) { - warn "Upgrading $table schema...\n"; - - my $start = time; - - $class->_upgrade_schema(%opt); - - if ( $oldAutoCommit ) { - warn " committing\n"; - dbh->commit or die dbh->errstr; - } - - #warn "\e[1K\rUpgrading $table... done in ". (time-$start). " seconds\n"; - warn " done in ". (time-$start). " seconds\n"; - - } else { - warn "WARNING: asked for schema upgrade of $table,". - " but FS::$table has no _upgrade_schema method\n"; - } - - } - -} - -=item upgrade_schema_data - -=cut - -sub upgrade_schema_data { - my %opt = @_; - - tie my %hash, 'Tie::IxHash', - - #fix classnum character(1) - 'cust_bill_pkg_detail' => [], - - ; - - \%hash; - -} - -sub upgrade_sqlradius { - #my %opt = @_; - - my $conf = new FS::Conf; - - my @part_export = FS::part_export::sqlradius->all_sqlradius_withaccounting(); - - foreach my $part_export ( @part_export ) { - - my $errmsg = 'Error adding FreesideStatus to '. - $part_export->option('datasrc'). ': '; - - my $dbh = DBI->connect( - ( map $part_export->option($_), qw ( datasrc username password ) ), - { PrintError => 0, PrintWarn => 0 } - ) or do { - warn $errmsg.$DBI::errstr; - next; - }; - - my $str2time = str2time_sql( $dbh->{Driver}->{Name} ); - my $group = "UserName"; - $group .= ",Realm" - if ref($part_export) =~ /withdomain/ - || $dbh->{Driver}->{Name} =~ /^Pg/; #hmm - - my $sth_alter = $dbh->prepare( - "ALTER TABLE radacct ADD COLUMN FreesideStatus varchar(32) NULL" - ); - if ( $sth_alter ) { - if ( $sth_alter->execute ) { - my $sth_update = $dbh->prepare( - "UPDATE radacct SET FreesideStatus = 'done' WHERE FreesideStatus IS NULL" - ) or die $errmsg.$dbh->errstr; - $sth_update->execute or die $errmsg.$sth_update->errstr; - } else { - my $error = $sth_alter->errstr; - warn $errmsg.$error - unless $error =~ /Duplicate column name/i #mysql - || $error =~ /already exists/i; #Pg -; - } - } else { - my $error = $dbh->errstr; - warn $errmsg.$error; #unless $error =~ /exists/i; - } - - my $sth_index = $dbh->prepare( - "CREATE INDEX FreesideStatus ON radacct ( FreesideStatus )" - ); - if ( $sth_index ) { - unless ( $sth_index->execute ) { - my $error = $sth_index->errstr; - warn $errmsg.$error - unless $error =~ /Duplicate key name/i #mysql - || $error =~ /already exists/i; #Pg - } - } else { - my $error = $dbh->errstr; - warn $errmsg.$error. ' (preparing statement)';#unless $error =~ /exists/i; - } - - my $times = ($dbh->{Driver}->{Name} =~ /^mysql/) - ? ' AcctStartTime != 0 AND AcctStopTime != 0 ' - : ' AcctStartTime IS NOT NULL AND AcctStopTime IS NOT NULL '; - - my $sth = $dbh->prepare("SELECT UserName, - Realm, - $str2time max(AcctStartTime)), - $str2time max(AcctStopTime)) - FROM radacct - WHERE FreesideStatus = 'done' - AND $times - GROUP BY $group - ") - or die $errmsg.$dbh->errstr; - $sth->execute() or die $errmsg.$sth->errstr; - - while (my $row = $sth->fetchrow_arrayref ) { - my ($username, $realm, $start, $stop) = @$row; - - $username = lc($username) unless $conf->exists('username-uppercase'); - - my $exportnum = $part_export->exportnum; - my $extra_sql = " AND exportnum = $exportnum ". - " AND exportsvcnum IS NOT NULL "; - - if ( ref($part_export) =~ /withdomain/ ) { - $extra_sql = " AND '$realm' = ( SELECT domain FROM svc_domain - WHERE svc_domain.svcnum = svc_acct.domsvc ) "; - } - - my $svc_acct = qsearchs({ - 'select' => 'svc_acct.*', - 'table' => 'svc_acct', - 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum )'. - 'LEFT JOIN export_svc USING ( svcpart )', - 'hashref' => { 'username' => $username }, - 'extra_sql' => $extra_sql, - }); - - if ($svc_acct) { - $svc_acct->last_login($start) - if $start && (!$svc_acct->last_login || $start > $svc_acct->last_login); - $svc_acct->last_logout($stop) - if $stop && (!$svc_acct->last_logout || $stop > $svc_acct->last_logout); - } - } - } - -} - -=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 73ce13f7a..000000000 --- a/FS/FS/XMLRPC.pm +++ /dev/null @@ -1,166 +0,0 @@ - package FS::XMLRPC; - -use strict; -use vars qw( $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 FS::Maestro; - -use Data::Dumper; - -$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 []; - } - - } - - if ( scalar(@result) == 1 && ref($result[0]) eq 'HASH' ) { - return $result[0]; - } elsif (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 "Unhandled XMLRPC request '${method_name}'"; - return {}; - -} - -=head1 BUGS - -Probably lots. - -=head1 SEE ALSO - -L. - -=cut - -1; - diff --git a/FS/FS/Yori.pm b/FS/FS/Yori.pm deleted file mode 100644 index b5bdc0c16..000000000 --- a/FS/FS/Yori.pm +++ /dev/null @@ -1,94 +0,0 @@ -package FS::Yori; -# a reporting program, to report information to the MCP - -use strict; -use base 'Exporter'; - -our @EXPORT_OK = qw( reports report ); - -sub reports { #should be autogenerated i guess - qw( freeside_version debian_version pg_version - apache_version apache_mpm - payment_gateways - ); - #ssh_vulnkey -} - -sub report { - my $report = shift; - $report =~ /^(\w+)$/ or die; - eval "report_$report();"; -} - -sub report_all { - foreach my $report ( reports() ) { - print "$report: ". report($report). "\n"; - } -} - -sub report_freeside_version { - chomp( my $fs_version = - `grep '^VERSION=' /home/ivan/freeside/Makefile | cut -d= -f2` - ); - $fs_version; -} - -sub report_debian_version { - chomp( my $deb_version = `cat /etc/debian_version` ); - $deb_version; -} - -sub report_pg_version { - chomp( my $pg_version = `echo 'show server_version' | psql -t freeside` ); - chomp($pg_version); #two? - $pg_version =~ s/^ +//; - $pg_version; -} - -sub report_apache_version { - chomp( my $apache_version = - `/usr/sbin/apache2 -v | head -1 | cut -d: -f2 | cut -d/ -f2 | cut -d' ' -f1` - ); - $apache_version; -} - -sub report_apache_mpm { - chomp( my $apache_mpm = - `/usr/sbin/apache2 -V | grep '^Server MPM' | cut -d: -f2` - ); - $apache_mpm =~ s/^ +//; - $apache_mpm; -} - -sub report_payment_gateways { - my @gateways = split(/\n/, - `aptitude -F '%c %p' search 'libbusiness-onlinepayment-.*' | grep '^i ' | grep -v '^i libbusiness-onlinepayment-perl' | cut -c29- | cut -d- -f1` - ); - join(', ', @gateways); -} - -#sub report_ssh_vulnkey{ -# my $ssh_vulnkey = `ssh-vulnkey -a | grep COMPROMISED`; -# $ssh_vulnkey; -#} - -sub report_load { - open LOAD, "; - close LOAD; - ($one, $five, $fifteen); -} - -sub report_freememory { - open MEM, ") { - /^(\w*):\s*(\d*) kB$/ || next; - next unless grep { $_ eq $1 } @interesting; - $free += $2; - } - close MEM; - $free; -} - diff --git a/FS/FS/access_group.pm b/FS/FS/access_group.pm deleted file mode 100644 index b5b693a8f..000000000 --- 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 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, 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 bacc01331..000000000 --- a/FS/FS/access_groupagent.pm +++ /dev/null @@ -1,146 +0,0 @@ -package FS::access_groupagent; - -use strict; -use vars qw( @ISA ); -use FS::Record qw( qsearch qsearchs ); -use FS::agent; -use FS::access_group; - -@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 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 } ); -} - -=item access_group - -Returns the associated FS::access_group object. - -=cut - -sub access_group { - my $self = shift; - qsearchs('access_group', { 'groupnum' => $self->groupnum } ); -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, 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 ef8cc6cd8..000000000 --- a/FS/FS/access_right.pm +++ /dev/null @@ -1,198 +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 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; -} - -# _upgrade_data -# -# Used by FS::Upgrade to migrate to a new database. - -sub _upgrade_data { # class method - my ($class, %opts) = @_; - - my @unmigrated = ( qsearch( 'access_right', - { 'righttype'=>'FS::access_group', - 'rightname'=>'Engineering configuration', - } - ), - qsearch( 'access_right', - { 'righttype'=>'FS::access_group', - 'rightname'=>'Engineering global configuration', - } - ) - ); - foreach ( @unmigrated ) { - my $rightname = $_->rightname; - $rightname =~ s/Engineering/Dialup/; - $_->rightname($rightname); - my $error = $_->replace; - die "Failed to update access right: $error" - if $error; - my $broadband = new FS::access_right { $_->hash }; - $rightname =~ s/Dialup/Broadband/; - $broadband->rightnum(''); - $broadband->rightname($rightname); - $error = $broadband->insert; - die "Failed to insert access right: $error" - if $error; - } - - my %migrate = ( - 'Post payment' => [ 'Post check payment', 'Post cash payment' ], - 'Process payment' => [ 'Process credit card payment', 'Process Echeck payment' ], - 'Post refund' => [ 'Post check refund', 'Post cash refund' ], - 'Refund payment' => [ 'Refund credit card payment', 'Refund Echeck payment' ], - ); - - foreach my $oldright (keys %migrate) { - my @old = qsearch('access_right', { 'righttype'=>'FS::access_group', - 'rightname'=>$oldright, - } - ); - - foreach my $old ( @old ) { - - foreach my $newright ( @{ $migrate{$oldright} } ) { - my %hash = ( - 'righttype' => 'FS::access_group', - 'rightobjnum' => $old->rightobjnum, - 'rightname' => $newright, - ); - next if qsearchs('access_right', \%hash); - my $access_right = new FS::access_right \%hash; - my $error = $access_right->insert; - die $error if $error; - } - - #after the WEST stuff is sorted, etc. - #my $error = $old->delete; - #die $error if $error; - - } - - } - - ''; - -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, 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 075733a68..000000000 --- a/FS/FS/access_user.pm +++ /dev/null @@ -1,544 +0,0 @@ -package FS::access_user; - -use strict; -use base qw( FS::m2m_Common FS::option_Common ); -use vars qw( $DEBUG $me $conf $htpasswd_file ); -use FS::UID; -use FS::Conf; -use FS::Record qw( qsearch qsearchs dbh ); -use FS::access_user_pref; -use FS::access_usergroup; -use FS::agent; -use FS::cust_main; - -$DEBUG = 0; -$me = '[FS::access_user]'; - -#kludge htpasswd for now (i hope this bootstraps okay) -FS::UID->install_callback( sub { - $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 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; - - return '' if $self->is_system_user; - - 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; - } - } elsif ( $old->disabled && !$new->disabled - && $new->_password =~ /changeme/i ) { - return "Must change password when enabling this account"; - } - - 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_foreign_keyn('user_custnum', 'cust_main', 'custnum') - || $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; - return $self->username - if $self->get('last') eq 'Lastname' && $self->first eq 'Firstname'; - return $self->get('last'). ', '. $self->first; -} - -=item user_cust_main - -Returns the FS::cust_main object (see L), if any, for this -user. - -=cut - -sub user_cust_main { - my $self = shift; - qsearchs( 'cust_main', { 'custnum' => $self->user_custnum } ); -} - -=item access_usergroup - -Returns links to the the groups this user is a part of, as FS::access_usergroup -objects (see L). - -=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. - -=item viewall_right - -All agents will be viewable if the current user has the provided access right. -Defaults to 'View customers of all agents'. - -=back - -=cut - -sub agentnums_sql { - my( $self ) = shift; - my %opt = ref($_[0]) ? %{$_[0]} : @_; - - my $agentnum = $opt{'table'} ? $opt{'table'}.'.agentnum' : 'agentnum'; - - my @or = (); - - my $viewall_right = $opt{'viewall_right'} || 'View customers of all agents'; - if ( $self->access_right($viewall_right) ) { - push @or, "$agentnum IS NOT NULL"; - } else { - push @or, "$agentnum IN (". join(',', $self->agentnums). ')'; - } - - push @or, "$agentnum IS NULL" - if $opt{'null'} - || ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) ); - - return ' 1 = 0 ' unless scalar(@or); - '( '. join( ' OR ', @or ). ' )'; - -} - -=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 [ HASHREF | OPTION => VALUE ... ] - -Returns the list of agents this user can view (via group membership), as -FS::agent objects. Accepts the same options as the agentnums_sql method. - -=cut - -sub agents { - my $self = shift; - qsearch({ - 'table' => 'agent', - 'hashref' => { disabled=>'' }, - 'extra_sql' => ' AND '. $self->agentnums_sql(@_), - }); -} - -=item access_right RIGHTNAME | LISTREF - -Given a right name or a list reference of right names, returns true if this -user has this right, or, for a list, one of the rights (currently via group -membership, eventually also via user overrides). - -=cut - -sub access_right { - my( $self, $rightname ) = @_; - - $rightname = [ $rightname ] unless ref($rightname); - - warn "$me access_right called on ". join(', ', @$rightname). "\n" - if $DEBUG; - - #some caching of ACL requests for low-hanging fruit perf improvement - #since we get a new $CurrentUser object each page view there shouldn't be any - #issues with stickiness - if ( $self->{_ACLcache} ) { - - unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) { - warn "$me ACL cache hit for ". join(', ', @$rightname). "\n" - if $DEBUG; - return grep $self->{_ACLcache}{$_}, @$rightname - } - - warn "$me ACL cache miss for ". join(', ', @$rightname). "\n" - if $DEBUG; - - } else { - - warn "initializing ACL cache\n" - if $DEBUG; - $self->{_ACLcache} = {}; - - } - - my $has_right = ' rightname IN ('. join(',', map '?', @$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 $has_right - LIMIT 1 - ") or die dbh->errstr; - $sth->execute($self->usernum, @$rightname) or die $sth->errstr; - my $row = $sth->fetchrow_arrayref; - - my $return = $row ? $row->[0] : ''; - - #just caching the single-rightname hits should be enough of a win for now - if ( scalar(@$rightname) == 1 ) { - $self->{_ACLcache}{${$rightname}[0]} = $return; - } - - $return; - -} - -=item default_customer_view - -Returns the default customer view for this user, from the -"default_customer_view" user preference, the "cust_main-default_view" config, -or the hardcoded default, "jumbo" (may change to "basics" in the near future). - -=cut - -sub default_customer_view { - my $self = shift; - - $self->option('default_customer_view') - || $conf->config('cust_main-default_view') - || 'jumbo'; #'basics' in 1.9.1? - -} - -=item is_system_user - -Returns true if this user has the name of a known system account. These -users will not appear in the htpasswd file and can't have passwords set. - -=cut - -sub is_system_user { - my $self = shift; - return grep { $_ eq $self->username } ( qw( - fs_queue - fs_daily - fs_selfservice - fs_signup - fs_bootstrap - fs_selfserv -) ); -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, 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 a445d3115..000000000 --- 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) - -=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 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, L, 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 8511fe5be..000000000 --- a/FS/FS/access_usergroup.pm +++ /dev/null @@ -1,143 +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 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 - -=head1 SEE ALSO - -L, 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 ef0a27533..000000000 --- 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 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, 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 9816de965..000000000 --- a/FS/FS/acct_snarf.pm +++ /dev/null @@ -1,215 +0,0 @@ -package FS::acct_snarf; - -use strict; -use vars qw( @ISA ); -use Tie::IxHash; -use FS::Record qw( qsearchs ); -use FS::cust_svc; - -@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 snarfname - Label - -=item svcnum - Account (see L) - -=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 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 cust_svc - -=cut - -sub cust_svc { - my $self = shift; - qsearchs('cust_svc', { 'svcnum' => $self->svcnum } ); -} - - -=item svc_export - -Calls the replace export for any communigate exports attached to this rule's -service. - -=cut - -sub svc_export { - my $self = shift; - - my $cust_svc = $self->cust_svc; - my $svc_x = $cust_svc->svc_x; - - #_singledomain too - my @exports = $cust_svc->part_svc->part_export('communigate_pro'); - my @errors = map $_->export_replace($svc_x, $svc_x), @exports; - - @errors ? join(' / ', @errors) : ''; - -} - -=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_textn('snarfname') #alphasn? - || $self->ut_number('svcnum') - || $self->ut_foreign_key('svcnum', 'svc_acct', 'svcnum') - || $self->ut_domain('machine') - || $self->ut_alphan('protocol') - || $self->ut_textn('username') - || $self->ut_numbern('check_freq') - || $self->ut_enum('leavemail', [ '', 'Y' ]) - || $self->ut_enum('apop', [ '', 'Y' ]) - || $self->ut_enum('tls', [ '', 'Y' ]) - || $self->ut_alphan('mailbox') - ; - return $error if $error; - - $self->_password =~ /^[^\t\n]*$/ or return "illegal password"; - $self->_password($1); - - ''; #no error -} - -sub check_freq_labels { - - tie my %hash, 'Tie::IxHash', - 0 => 'Never', - 60 => 'minute', - 120 => '2 minutes', - 180 => '3 minutes', - 300 => '5 minutes', - 600 => '10 minutes', - 900 => '15 minutes', - 1800 => '30 minutes', - 3600 => 'hour', - 7200 => '2 hours', - 10800 => '3 hours', - 21600 => '6 hours', - 43200 => '12 hours', - 86400 => 'day', - 172800 => '2 days', - 259200 => '3 days', - 604800 => 'week', - 1000000000 => 'Disabled', - ; - - \%hash; -} - -=item cgp_hashref - -Returns a hashref representing this external mail account, suitable for -Communigate Pro API commands: - -=cut - -sub cgp_hashref { - my $self = shift; - { - 'authName' => $self->username, - 'domain' => $self->machine, - 'password' => $self->_password, - 'period' => $self->check_freq.'s', - 'APOP' => ( $self->apop eq 'Y' ? 'YES' : 'NO' ), - 'TLS' => ( $self->tls eq 'Y' ? 'YES' : 'NO' ), - 'Leave' => ( $self->leavemail eq 'Y' ? 'YES' : 'NO' ), #XXX leave?? - }; -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, 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 0fe2476a2..000000000 --- a/FS/FS/addr_block.pm +++ /dev/null @@ -1,385 +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; -use Carp qw( carp ); - -@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. - -=item manual_flag - prohibit automatic ip assignment from this block when true. - -=item agentnum - optional agent number (see L) - -=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. - -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. - -sub replace_check { - my ( $new, $old ) = ( shift, shift ); - - unless($new->routernum == $old->routernum) { - my @svc = $self->svc_broadband; - if (@svc) { - return 'Block has assigned addresses: '. - join ', ', map {$_->ip_addr} @svc; - } - - return 'Block is already allocated' - if($new->routernum && $old->routernum); - - } - - ''; -} - -=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') - || $self->ut_enum('manual_flag', [ '', 'Y' ]) - || $self->ut_agentnum_acl('agentnum', 'Broadband global configuration') - ; - 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. There are never free addresses -when manual_flag is true. - -=cut - -sub next_free_addr { - my $self = shift; - - return '' if $self->manual_flag; - - 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 -- deprecated - -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) = @_; - carp "deallocate deprecated -- use replace"; - - return 'Block must be allocated to a router' - unless(ref $router eq 'FS::router'); - - my $new = new FS::addr_block {$self->hash}; - $new->routernum($router->routernum); - return $new->replace($self); - -} - -=item deallocate -- deprecated - -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 { - carp "deallocate deprecated -- use replace"; - my $self = shift; - - 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 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. - -=item agent - -Returns the agent (see L) for this address block, if one exists. - -=cut - -sub agent { - qsearchs('agent', { 'agentnum' => shift->agentnum } ); -} - -=item label - -Returns text including the router name, gateway ip, and netmask for this -block. - -=cut - -sub label { - my $self = shift; - my $router = $self->router; - ($router ? $router->routername : '(unallocated)'). ':'. $self->NetAddr; -} - -=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 3794d3f1d..000000000 --- a/FS/FS/agent.pm +++ /dev/null @@ -1,592 +0,0 @@ -package FS::agent; - -use strict; -use vars qw( @ISA ); -#use Crypt::YAPassGen; -use Business::CreditCard 0.28; -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; -use FS::Conf; - -@ISA = qw( FS::m2m_Common 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) - -=item ticketing_queueid - Ticketing Queue - -=item invoice_template - Invoice template name - -=item agent_custnum - Optional agent customer (see L) - -=item disabled - Disabled flag, empty or 'Y' - -=item prog - Deprecated (never used) - -=item freq - Deprecated (never used) - -=item username - (Deprecated) Username for the Agent interface - -=item _password - (Deprecated) 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') - || $self->ut_foreign_keyn('agent_custnum', 'cust_main', 'custnum' ) - ; - 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) for this agent. - -=cut - -sub agent_type { - my $self = shift; - qsearchs( 'agent_type', { 'typenum' => $self->typenum } ); -} - -=item agent_cust_main - -Returns the FS::cust_main object (see L), if any, for this -agent. - -=cut - -sub agent_cust_main { - my $self = shift; - qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } ); -} - -=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. - -=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 -field, or the empty string. - -=cut - -sub ticketing_queue { - my $self = shift; - FS::TicketSystem->queue($self->ticketing_queueid); -}; - -=item payment_gateway [ OPTION => VALUE, ... ] - -Returns a payment gateway object (see L) for this agent. - -Currently available options are I, I, I, and I. - -If I is set, and no gateway is available, then the empty string -will be returned instead of throwing a fatal exception. - -If I is set to the number of an invoice (see L) then -an attempt will be made to select a gateway suited for the taxes paid on -the invoice. - -The I and I options can be used to influence the choice -as well. Presently only 'CC' and 'ECHECK' methods are meaningful. - -When the I is 'CC' then the card number in I can direct -this routine to route to a gateway suited for that type of card. - -=cut - -sub payment_gateway { - my ( $self, %options ) = @_; - - my $taxclass = ''; - if ( $options{invnum} ) { - - my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{invnum} } ); - die "invnum ". $options{'invnum'}. " not found" unless $cust_bill; - - my @part_pkg = - map { $_->part_pkg } - grep { $_ } - map { $_->cust_pkg } - $cust_bill->cust_bill_pkg; - - my @taxclasses = map $_->taxclass, @part_pkg; - - $taxclass = $taxclasses[0] - unless grep { $taxclasses[0] ne $_ } @taxclasses; #unless there are - #different taxclasses - } - - #look for an agent gateway override first - my $cardtype; - if ( $options{method} && $options{method} eq 'CC' && $options{payinfo} ) { - $cardtype = cardtype($options{payinfo}); - } elsif ( $options{method} && $options{method} eq 'ECHECK' ) { - $cardtype = 'ACH'; - } else { - $cardtype = $options{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 $conf = new FS::Conf; - if ( $override ) { #use a payment gateway override - - $payment_gateway = $override->payment_gateway; - - $payment_gateway->gateway_namespace('Business::OnlinePayment') - unless $payment_gateway->gateway_namespace; - - } else { #use the standard settings from the config - - # the standard settings from the config could be moved to a null agent - # agent_payment_gateway referenced payment_gateway - - unless ( $conf->exists('business-onlinepayment') ) { - if ( $options{'nofatal'} ) { - return ''; - } else { - die "Real-time processing not enabled\n"; - } - } - - #load up config - my $bop_config = 'business-onlinepayment'; - $bop_config .= '-ach' - if ( $options{method} - && $options{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; - - $payment_gateway = new FS::payment_gateway; - - $payment_gateway->gateway_namespace( $conf->config('business-onlinepayment-namespace') || - 'Business::OnlinePayment'); - $payment_gateway->gateway_module($processor); - $payment_gateway->gateway_username($login); - $payment_gateway->gateway_password($password); - $payment_gateway->gateway_action($action); - $payment_gateway->set('options', [ @bop_options ]); - - } - - unless ( $payment_gateway->gateway_namespace ) { - $payment_gateway->gateway_namespace( - scalar($conf->config('business-onlinepayment-namespace')) - || 'Business::OnlinePayment' - ); - } - - $payment_gateway; -} - -=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, L, L, L, -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 bd99d0ccd..000000000 --- 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 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, L, L, 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 5d6b94e0c..000000000 --- a/FS/FS/agent_type.pm +++ /dev/null @@ -1,195 +0,0 @@ -package FS::agent_type; - -use strict; -use vars qw( @ISA ); -use FS::Record qw( qsearch dbh ); -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) has an agent type. Agent types define which packages (see -L) may be purchased by customers (see L), via -FS::type_pkgs records (see L). 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. - -=cut - -sub pkgpart_hashref { - my $self = shift; - my %pkgpart; - $pkgpart{$_}++ foreach $self->pkgpart; - \%pkgpart; -} - -=item type_pkgs - -Returns all FS::type_pkgs objects (see L) 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) that link to enabled -package definitions (see L). - -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) for this -agent type. - -=cut - -sub pkgpart { - my $self = shift; - - #map $_->pkgpart, $self->type_pkgs; - - my $sql = 'SELECT pkgpart FROM type_pkgs WHERE typenum = ?'; - my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute( $self->typenum ) or die $sth->errstr; - map $_->[0], @{ $sth->fetchall_arrayref }; -} - -=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, L, L, L, -L, 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 337965324..000000000 --- a/FS/FS/banned_pay.pm +++ /dev/null @@ -1,141 +0,0 @@ -package FS::banned_pay; - -use strict; -use base qw( FS::otaker_Mixin FS::Record ); -use FS::Record qw( qsearch qsearchs ); -use FS::UID qw( getotaker ); -use FS::CurrentUser; - -=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 or I - -=item payinfo - fingerprint of banned card (base64-encoded MD5 digest) - -=item _date - specified as a UNIX timestamp; see L. Also see -L and L for conversion functions. - -=item usernum - order taker (assigned automatically, see L) - -=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 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->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; - - $self->SUPER::check; -} - -# Used by FS::Upgrade to migrate to a new database. -sub _upgrade_data { # class method - my ($class, %opts) = @_; - $class->_upgrade_otaker(%opts); -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/bill_batch.pm b/FS/FS/bill_batch.pm deleted file mode 100644 index 136db0d9e..000000000 --- a/FS/FS/bill_batch.pm +++ /dev/null @@ -1,151 +0,0 @@ -package FS::bill_batch; - -use strict; -use vars qw( @ISA $me $DEBUG ); -use FS::Record qw( qsearch qsearchs dbh ); -use FS::cust_bill_batch; - -@ISA = qw( FS::Record ); -$me = '[ FS::bill_batch ]'; -$DEBUG=0; - -sub table { 'bill_batch' } - -sub nohistory_fields { 'pdf' } - -=head1 NAME - -FS::bill_batch - Object methods for bill_batch records - -=head1 SYNOPSIS - - use FS::bill_batch; - - $open_batch = FS::bill_batch->get_open_batch; - - my $pdf = $open_batch->print_pdf; - - $error = $open_batch->close; - -=head1 DESCRIPTION - -An FS::bill_batch object represents a batch of invoices. FS::bill_batch -inherits from FS::Record. The following fields are currently supported: - -=over 4 - -=item batchnum - primary key - -=item status - either 'O' (open) or 'R' (resolved/closed). - -=item pdf - blob field for temporarily storing the invoice as a PDF. - -=back - -=head1 METHODS - -=over 4 - -=item print_pdf - -Typeset the entire batch as a PDF file. Returns the PDF as a string. - -=cut - -sub print_pdf { - eval 'use CAM::PDF'; - warn "Failed to load CAM::PDF: '$@'\n" if $@; - - my $self = shift; - my $job = shift; - $job->update_statustext(0) if $job; - my @invoices = sort { $a->invnum <=> $b->invnum } - qsearch('cust_bill_batch', { batchnum => $self->batchnum }); - return "No invoices in batch ".$self->batchnum.'.' if !@invoices; - - my $pdf_out; - my $num = 0; - foreach my $invoice (@invoices) { - my $part = $invoice->cust_bill->print_pdf({$invoice->options}); - die 'Failed creating PDF from invoice '.$invoice->invnum.'\n' if !$part; - - if($pdf_out) { - $pdf_out->appendPDF(CAM::PDF->new($part)); - } - else { - $pdf_out = CAM::PDF->new($part); - } - if($job) { - # update progressbar - $num++; - my $error = $job->update_statustext(int(100 * $num/scalar(@invoices))); - die $error if $error; - } - } - - return $pdf_out->toPDF; -} - -=item close - -Set the status of the batch to 'R' (resolved). - -=cut - -sub close { - my $self = shift; - $self->status('R'); - return $self->replace; -} - -=back - -=head1 CLASS METHODS - -=item get_open_batch - -Returns the currently open batch. There should only be one at a time. - -=cut - -sub get_open_batch { - my $class = shift; - my $batch = qsearchs('bill_batch', { status => 'O' }); - return $batch if $batch; - $batch = FS::bill_batch->new({status => 'O'}); - my $error = $batch->insert; - die $error if $error; - return $batch; -} - -use Storable 'thaw'; -use Data::Dumper; -use MIME::Base64; - -sub process_print_pdf { - my $job = shift; - my $param = thaw(decode_base64(shift)); - warn Dumper($param) if $DEBUG; - die "no batchnum specified!\n" if ! exists($param->{batchnum}); - my $batch = FS::bill_batch->by_key($param->{batchnum}); - die "batch '$param->{batchnum}' not found!\n" if !$batch; - - my $pdf = $batch->print_pdf($job); - $batch->pdf($pdf); - my $error = $batch->replace; - die $error if $error; -} - - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/category_Common.pm b/FS/FS/category_Common.pm deleted file mode 100644 index c239a7893..000000000 --- a/FS/FS/category_Common.pm +++ /dev/null @@ -1,87 +0,0 @@ -package FS::category_Common; - -use strict; -use base qw( FS::Record ); -use FS::Record qw( qsearch ); - -=head1 NAME - -FS::category_Common - Base class for category (group of classifications) classes - -=head1 SYNOPSIS - -use base qw( FS::category_Common ); -use FS::class_table; #should use this - -#optional for non-standard names -sub _class_table { 'table_name'; } #default is to replace s/category/class/ - -=head1 DESCRIPTION - -FS::category_Common is a base class for classes which provide a categorization -(group of classifications) for other classes, such as pkg_category or -cust_category. - -=item delete - -Deletes this category from the database. Only categories with no associated -classifications can be deleted. If there is an error, returns the error, -otherwise returns false. - -=cut - -sub delete { - my $self = shift; - - return "Can't delete a ". $self->table. - " with ". $self->_class_table. " records!" - if qsearch( $self->_class_table, { 'categorynum' => $self->categorynum } ); - - $self->SUPER::delete; -} - -=item check - -Checks all fields to make sure this is a valid package category. 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('categorynum') - or $self->ut_text('categoryname') - or $self->ut_snumbern('weight') - or $self->ut_enum('disabled', [ '', 'Y' ]) - or $self->SUPER::check; - -} - -=back - -=cut - -#defaults - -use vars qw( $_class_table ); -sub _class_table { - return $_class_table if $_class_table; - my $self = shift; - $_class_table = $self->table; - $_class_table =~ s/category/cclass/ # s/_category$/_class/ - or die "can't determine an automatic class table for $_class_table"; - $_class_table; -} - -=head1 BUGS - -=head1 SEE ALSO - -L - -=cut - -1; - diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm deleted file mode 100644 index f7402eead..000000000 --- a/FS/FS/cdr.pm +++ /dev/null @@ -1,950 +0,0 @@ -package FS::cdr; - -use strict; -use vars qw( @ISA @EXPORT_OK $DEBUG $me ); -use Exporter; -use Tie::IxHash; -use Date::Parse; -use Date::Format; -use Time::Local; -use FS::UID qw( dbh ); -use FS::Conf; -use FS::Record qw( qsearch qsearchs ); -use FS::cdr_type; -use FS::cdr_calltype; -use FS::cdr_carrier; -use FS::cdr_batch; -use FS::cdr_termination; - -@ISA = qw(FS::Record); -@EXPORT_OK = qw( _cdr_date_parser_maker _cdr_min_parser_maker ); - -$DEBUG = 0; -$me = '[FS::cdr]'; - -=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 (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 - -=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) - -=cut - -#Telstra =1, Optus = 2, RSL COM = 3 - -=item upstream_rateid - Upstream Rate ID - -=item svcnum - Link to customer service (see L) - -=item freesidestatus - NULL, done (or something) - -=item freesiderewritestatus - NULL, done (or something) - -=item cdrbatch - -=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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'cdr'; } - -sub table_info { - { - 'fields' => { -#XXX fill in some (more) nice names - #'acctid' => '', - 'calldate' => 'Call date', - 'clid' => 'Caller ID', - 'src' => 'Source', - 'dst' => 'Destination', - 'dcontext' => 'Dest. context', - 'channel' => 'Channel', - 'dstchannel' => 'Destination channel', - #'lastapp' => '', - #'lastdata' => '', - 'startdate' => 'Start date', - 'answerdate' => 'Answer date', - 'enddate' => 'End date', - 'duration' => 'Duration', - 'billsec' => 'Billable seconds', - 'disposition' => 'Disposition', - 'amaflags' => 'AMA flags', - 'accountcode' => 'Account code', - #'uniqueid' => '', - 'userfield' => 'User field', - #'cdrtypenum' => '', - 'charged_party' => 'Charged party', - #'upstream_currency' => '', - 'upstream_price' => 'Upstream price', - #'upstream_rateplanid' => '', - #'ratedetailnum' => '', - 'rated_price' => 'Rated price', - #'distance' => '', - #'islocal' => '', - #'calltypenum' => '', - #'description' => '', - #'quantity' => '', - 'carrierid' => 'Carrier ID', - #'upstream_rateid' => '', - 'svcnum' => 'Freeside service', - 'freesidestatus' => 'Freeside status', - 'freesiderewritestatus' => 'Freeside rewrite status', - 'cdrbatch' => 'Legacy batch', - 'cdrbatchnum' => '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 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') -# || $self->ut_textn('freesiderewritestatus') -# ; -# return $error if $error; - - for my $f ( grep { $self->$_ =~ /\D/ } qw(startdate answerdate enddate)){ - $self->$f( str2time($self->$f) ); - } - - $self->calldate( $self->startdate_sql ) - if !$self->calldate && $self->startdate; - - #was just for $format eq 'taqua' but can't see the harm... add something to - #disable if it becomes a problem - if ( $self->duration eq '' && $self->enddate && $self->startdate ) { - $self->duration( $self->enddate - $self->startdate ); - } - if ( $self->billsec eq '' && $self->enddate && $self->answerdate ) { - $self->billsec( $self->enddate - $self->answerdate ); - } - - $self->set_charged_party; - - #check the foreign keys even? - #do we want to outright *reject* the CDR? - my $error = - $self->ut_numbern('acctid') - - #add a config option to turn these back on if someone needs 'em - # - # #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 is_tollfree [ COLUMN ] - -Returns true when the cdr represents a toll free number and false otherwise. - -By default, inspects the dst field, but an optional column name can be passed -to inspect other field. - -=cut - -sub is_tollfree { - my $self = shift; - my $field = scalar(@_) ? shift : 'dst'; - ( $self->$field() =~ /^(\+?1)?8(8|([02-7])\3)/ ) ? 1 : 0; -} - -=item set_charged_party - -If the charged_party field is already set, does nothing. Otherwise: - -If the cdr-charged_party-accountcode config option is enabled, sets the -charged_party to the accountcode. - -Otherwise sets the charged_party normally: to the src field in most cases, -or to the dst field if it is a toll free number. - -=cut - -sub set_charged_party { - my $self = shift; - - my $conf = new FS::Conf; - - unless ( $self->charged_party ) { - - if ( $conf->exists('cdr-charged_party-accountcode') && $self->accountcode ){ - - my $charged_party = $self->accountcode; - $charged_party =~ s/^0+// - if $conf->exists('cdr-charged_party-accountcode-trim_leading_0s'); - $self->charged_party( $charged_party ); - - } elsif ( $conf->exists('cdr-charged_party-field') ) { - - my $field = $conf->config('cdr-charged_party-field'); - $self->charged_party( $self->$field() ); - - } else { - - if ( $self->is_tollfree ) { - $self->charged_party($self->dst); - } else { - $self->charged_party($self->src); - } - - } - - } - -# my $prefix = $conf->config('cdr-charged_party-truncate_prefix'); -# my $prefix_len = length($prefix); -# my $trunc_len = $conf->config('cdr-charged_party-truncate_length'); -# -# $self->charged_party( substr($self->charged_party, 0, $trunc_len) ) -# if $prefix_len && $trunc_len -# && substr($self->charged_party, 0, $prefix_len) eq $prefix; - -} - -=item set_status_and_rated_price STATUS [ RATED_PRICE [ SVCNUM ] ] - -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, $svcnum, %opt) = @_; - if($opt{'inbound'}) { - my $term = qsearchs('cdr_termination', { - acctid => $self->acctid, - termpart => 1 # inbound - }); - my $error; - if($term) { - warn "replacing existing cdr status (".$self->acctid.")\n" if $term; - $error = $term->delete; - return $error if $error; - } - $term = FS::cdr_termination->new({ - acctid => $self->acctid, - termpart => 1, - rated_price => $rated_price, - status => $status, - svcnum => $svcnum, - }); - return $term->insert; - } - else { - $self->freesidestatus($status); - $self->rated_price($rated_price); - $self->svcnum($svcnum) if $svcnum; - return $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), 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), 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 downstream_csv [ OPTION => VALUE, ... ] - -=cut - -my %export_names = ( - 'simple' => { - 'name' => 'Simple', - 'invoice_header' => "Date,Time,Name,Destination,Duration,Price", - }, - 'simple2' => { - 'name' => 'Simple with source', - 'invoice_header' => "Date,Time,Called From,Destination,Duration,Price", - #"Date,Time,Name,Called From,Destination,Duration,Price", - }, - 'default' => { - 'name' => 'Default', - 'invoice_header' => 'Date,Time,Number,Destination,Duration,Price', - }, - 'source_default' => { - 'name' => 'Default with source', - 'invoice_header' => 'Caller,Date,Time,Number,Destination,Duration,Price', - }, - 'accountcode_default' => { - 'name' => 'Default plus accountcode', - 'invoice_header' => 'Date,Time,Account,Number,Destination,Duration,Price', - }, -); - -my %export_formats = (); -sub export_formats { - #my $self = shift; - - return %export_formats if keys %export_formats; - - my $conf = new FS::Conf; - my $date_format = $conf->config('date_format') || '%m/%d/%Y'; - - # This is now smarter, and shows the call duration in the - # largest units that accurately reflect the granularity. - my $duration_sub = sub { - my($cdr, %opt) = @_; - my $sec = $opt{seconds} || $cdr->billsec; - if ( length($opt{granularity}) && - $opt{granularity} == 0 ) { #per call - return '1 call'; - } - elsif ( $opt{granularity} == 60 ) {#full minutes - return sprintf("%.0fm",$sec/60); - } - else { #anything else - return sprintf("%dm %ds", $sec/60, $sec%60); - } - }; - - %export_formats = ( - 'simple' => [ - sub { time2str($date_format, shift->calldate_unix ) }, #DATE - sub { time2str('%r', shift->calldate_unix ) }, #TIME - 'userfield', #USER - 'dst', #NUMBER_DIALED - $duration_sub, #DURATION - #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE - sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE - ], - 'simple2' => [ - sub { time2str($date_format, shift->calldate_unix ) }, #DATE - sub { time2str('%r', shift->calldate_unix ) }, #TIME - #'userfield', #USER - 'src', #called from - 'dst', #NUMBER_DIALED - $duration_sub, #DURATION - #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE - sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE - ], - 'default' => [ - - #DATE - sub { time2str($date_format, shift->calldate_unix ) }, - # #time2str("%Y %b %d - %r", $cdr->calldate_unix ), - - #TIME - sub { time2str('%r', shift->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 - - #DEST ("Number") - sub { my($cdr, %opt) = @_; $opt{pretty_dst} || $cdr->dst; }, - - #REGIONNAME ("Destination") - sub { my($cdr, %opt) = @_; $opt{dst_regionname}; }, - - #DURATION - $duration_sub, - - #PRICE - sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, - - ], - ); - $export_formats{'source_default'} = [ 'src', @{ $export_formats{'default'} }, ]; - $export_formats{'accountcode_default'} = - [ @{ $export_formats{'default'} }[0,1], - 'accountcode', - @{ $export_formats{'default'} }[2..5], - ]; - - %export_formats -} - -sub downstream_csv { - my( $self, %opt ) = @_; - - my $format = $opt{'format'}; - my %formats = $self->export_formats; - return "Unknown format $format" unless exists $formats{$format}; - - #my $conf = new FS::Conf; - #$opt{'money_char'} ||= $conf->config('money_char') || '$'; - $opt{'money_char'} ||= FS::Conf->new->config('money_char') || '$'; - - eval "use Text::CSV_XS;"; - die $@ if $@; - my $csv = new Text::CSV_XS; - - my @columns = - map { - ref($_) ? &{$_}($self, %opt) : $self->$_(); - } - @{ $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 invoice_formats - -Returns an ordered list of key value pairs containing invoice format names -as keys (for use with part_pkg::voip_cdr) and "pretty" format names as values. - -=cut - -sub invoice_formats { - map { ($_ => $export_names{$_}->{'name'}) } - grep { $export_names{$_}->{'invoice_header'} } - keys %export_names; -} - -=item invoice_header FORMAT - -Returns a scalar containing the CSV column header for invoice format FORMAT. - -=cut - -sub invoice_header { - my $format = shift; - $export_names{$format}->{'invoice_header'}; -} - -=item import_formats - -Returns an ordered list of key value pairs containing import format names -as keys (for use with batch_import) and "pretty" format names as values. - -=cut - -#false laziness w/part_pkg & part_export - -my %cdr_info; -foreach my $INC ( @INC ) { - warn "globbing $INC/FS/cdr/*.pm\n" if $DEBUG; - foreach my $file ( glob("$INC/FS/cdr/*.pm") ) { - warn "attempting to load CDR format info from $file\n" if $DEBUG; - $file =~ /\/(\w+)\.pm$/ or do { - warn "unrecognized file in $INC/FS/cdr/: $file\n"; - next; - }; - my $mod = $1; - my $info = eval "use FS::cdr::$mod; ". - "\\%FS::cdr::$mod\::info;"; - if ( $@ ) { - die "error using FS::cdr::$mod (skipping): $@\n" if $@; - next; - } - unless ( keys %$info ) { - warn "no %info hash found in FS::cdr::$mod, skipping\n"; - next; - } - warn "got CDR format info from FS::cdr::$mod: $info\n" if $DEBUG; - if ( exists($info->{'disabled'}) && $info->{'disabled'} ) { - warn "skipping disabled CDR format FS::cdr::$mod" if $DEBUG; - next; - } - $cdr_info{$mod} = $info; - } -} - -tie my %import_formats, 'Tie::IxHash', - map { $_ => $cdr_info{$_}->{'name'} } - sort { $cdr_info{$a}->{'weight'} <=> $cdr_info{$b}->{'weight'} } - grep { exists($cdr_info{$_}->{'import_fields'}) } - keys %cdr_info; - -sub import_formats { - %import_formats; -} - -sub _cdr_min_parser_maker { - my $field = shift; - my @fields = ref($field) ? @$field : ($field); - @fields = qw( billsec duration ) unless scalar(@fields) && $fields[0]; - return sub { - my( $cdr, $min ) = @_; - my $sec = eval { _cdr_min_parse($min) }; - die "error parsing seconds for @fields from $min minutes: $@\n" if $@; - $cdr->$_($sec) foreach @fields; - }; -} - -sub _cdr_min_parse { - my $min = shift; - sprintf('%.0f', $min * 60 ); -} - -sub _cdr_date_parser_maker { - my $field = shift; - my %options = @_; - my @fields = ref($field) ? @$field : ($field); - return sub { - my( $cdr, $datestring ) = @_; - my $unixdate = eval { _cdr_date_parse($datestring, %options) }; - die "error parsing date for @fields from $datestring: $@\n" if $@; - $cdr->$_($unixdate) foreach @fields; - }; -} - -sub _cdr_date_parse { - my $date = shift; - my %options = @_; - - return '' unless length($date); #that's okay, it becomes NULL - return '' if $date eq 'NA'; #sansay - - if ( $date =~ /^([a-z]{3})\s+([a-z]{3})\s+(\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})\s+(\d{4})$/i && $7 > 1970 ) { - my $time = str2time($date); - return $time if $time > 100000; #just in case - } - - my($year, $mon, $day, $hour, $min, $sec); - - #$date =~ /^\s*(\d{4})[\-\/]\(\d{1,2})[\-\/](\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})\s*$/ - #taqua #2007-10-31 08:57:24.113000000 - - if ( $date =~ /^\s*(\d{4})\D(\d{1,2})\D(\d{1,2})\D+(\d{1,2})\D(\d{1,2})\D(\d{1,2})(\D|$)/ ) { - ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); - } elsif ( $date =~ /^\s*(\d{1,2})\D(\d{1,2})\D(\d{4})\s+(\d{1,2})\D(\d{1,2})(?:\D(\d{1,2}))?(\D|$)/ ) { - # 8/26/2010 12:20:01 - # optionally without seconds - ($mon, $day, $year, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); - $sec = 0 if !defined($sec); - } elsif ( $date =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d+\.\d+)(\D|$)/ ) { - # broadsoft: 20081223201938.314 - ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); - } elsif ( $date =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\d+(\D|$)/ ) { - # Taqua OM: 20050422203450943 - ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); - } elsif ( $date =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/ ) { - # WIP: 20100329121420 - ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); - } elsif ( $date =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/) { - # Telos - ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); - $options{gmt} = 1; - } else { - die "unparsable date: $date"; #maybe we shouldn't die... - } - - return '' if ( $year == 1900 || $year == 1970 ) && $mon == 1 && $day == 1 - && $hour == 0 && $min == 0 && $sec == 0; - - if ($options{gmt}) { - timegm($sec, $min, $hour, $day, $mon-1, $year); - } else { - timelocal($sec, $min, $hour, $day, $mon-1, $year); - } -} - -=item batch_import HASHREF - -Imports CDR records. Available options are: - -=over 4 - -=item file - -Filename - -=item format - -=item params - -Hash reference of preset fields, typically cdrbatch - -=item empty_ok - -Set true to prevent throwing an error on empty imports - -=back - -=cut - -my %import_options = ( - 'table' => 'cdr', - - 'batch_keycol' => 'cdrbatchnum', - 'batch_table' => 'cdr_batch', - 'batch_namecol' => 'cdrbatch', - - 'formats' => { map { $_ => $cdr_info{$_}->{'import_fields'}; } - keys %cdr_info - }, - - #drop the || 'csv' to allow auto xls for csv types? - 'format_types' => { map { $_ => ( lc($cdr_info{$_}->{'type'}) || 'csv' ); } - keys %cdr_info - }, - - 'format_headers' => { map { $_ => ( $cdr_info{$_}->{'header'} || 0 ); } - keys %cdr_info - }, - - 'format_sep_chars' => { map { $_ => $cdr_info{$_}->{'sep_char'}; } - keys %cdr_info - }, - - 'format_fixedlength_formats' => - { map { $_ => $cdr_info{$_}->{'fixedlength_format'}; } - keys %cdr_info - }, - - 'format_xml_formats' => - { map { $_ => $cdr_info{$_}->{'xml_format'}; } - keys %cdr_info - }, - - 'format_row_callbacks' => { map { $_ => $cdr_info{$_}->{'row_callback'}; } - keys %cdr_info - }, -); - -sub _import_options { - \%import_options; -} - -sub batch_import { - my $opt = shift; - - my $iopt = _import_options; - $opt->{$_} = $iopt->{$_} foreach keys %$iopt; - - FS::Record::batch_import( $opt ); - -} - -=item process_batch_import - -=cut - -sub process_batch_import { - my $job = shift; - - my $opt = _import_options; -# $opt->{'params'} = [ 'format', 'cdrbatch' ]; - - FS::Record::process_batch_import( $job, $opt, @_ ); - -} -# if ( $format eq 'simple' ) { #should be a callback or opt in FS::cdr::simple -# @columns = map { s/^ +//; $_; } @columns; -# } - -# _ upgrade_data -# -# Used by FS::Upgrade to migrate to a new database. - -sub _upgrade_data { - my ($class, %opts) = @_; - - warn "$me upgrading $class\n" if $DEBUG; - - my $sth = dbh->prepare( - 'SELECT DISTINCT(cdrbatch) FROM cdr WHERE cdrbatch IS NOT NULL' - ) or die dbh->errstr; - - $sth->execute or die $sth->errstr; - - my %cdrbatchnum = (); - while (my $row = $sth->fetchrow_arrayref) { - - my $cdr_batch = qsearchs( 'cdr_batch', { 'cdrbatch' => $row->[0] } ); - unless ( $cdr_batch ) { - $cdr_batch = new FS::cdr_batch { 'cdrbatch' => $row->[0] }; - my $error = $cdr_batch->insert; - die $error if $error; - } - - $cdrbatchnum{$row->[0]} = $cdr_batch->cdrbatchnum; - } - - $sth = dbh->prepare('UPDATE cdr SET cdrbatch = NULL, cdrbatchnum = ? WHERE cdrbatch IS NOT NULL AND cdrbatch = ?') or die dbh->errstr; - - foreach my $cdrbatch (keys %cdrbatchnum) { - $sth->execute($cdrbatchnum{$cdrbatch}, $cdrbatch) or die $sth->errstr; - } - -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/cdr/asterisk.pm b/FS/FS/cdr/asterisk.pm deleted file mode 100644 index 8b29642ea..000000000 --- a/FS/FS/cdr/asterisk.pm +++ /dev/null @@ -1,45 +0,0 @@ -package FS::cdr::asterisk; - -use strict; -use vars qw(@ISA %info); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -#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, -); - -%info = ( - 'name' => 'Asterisk', - 'weight' => 10, - 'import_fields' => [ - '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', - ], -); - -1; diff --git a/FS/FS/cdr/bell_west.pm b/FS/FS/cdr/bell_west.pm deleted file mode 100644 index f745bb190..000000000 --- a/FS/FS/cdr/bell_west.pm +++ /dev/null @@ -1,122 +0,0 @@ -package FS::cdr::bell_west; - -use strict; -use base qw( FS::cdr ); -use vars qw( %info $tmp_mon $tmp_mday $tmp_year ); -use Time::Local; -#use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker ); - -%info = ( - 'name' => 'Bell West', - 'weight' => 210, - 'header' => 1, - 'type' => 'xls', - - 'import_fields' => [ - - # CDR FIELD / REQUIRED / Notes - - # CHG TYPE / No / Internal Code only (no need to import) - sub {}, - - # ACCOUNT # / No / Internal Number only (no need to import) - sub {}, - - # DATE / Yes / "DATE" Excel date format MM/DD/YYYY - # XXX false laziness w/troop.pm - sub { my($cdr, $date) = @_; - - my $datetime = DateTime::Format::Excel->parse_datetime( $date ); - $tmp_mon = $datetime->mon_0; - $tmp_mday = $datetime->mday; - $tmp_year = $datetime->year; - }, - - # CUST NO / Yes / "TIME" "075959" Text based time - # Note: This is really the start time but Bell header says "Cust No" which - # is wrong - sub { my($cdr, $time) = @_; - #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate); - $time =~ /^(\d{2})(\d{2})(\d{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) - ); - }, - - # BTN / Yes / Main billing number but not DID or real number - # (put in SRC field) - 'src', - - # ORIG CITY / No / We will use your Freeside rating and description name - 'channel', - - # TERM / YES / All calls should be billed, however all calls are - # missing "1+" and "011+" & DIR ASST = "411" - 'dst', - - # TERM CITY / No / We will use your Freeside rating and description name - 'dstchannel', - - # WTN / Yes / Bill to number (put in "charged_party") - 'charged_party', - - # CODE / Yes / Account Code (security) and we need on invoice - 'accountcode', - - # PROV/COUNTRY / No / We will use your Freeside rating and description name - # (but use this to add "011" for "International" calls) - sub { my( $cdr, $prov ) = @_; - my $pre = ( $prov =~ /^\s*International\s*/i ) ? '011' : '1'; - $cdr->dst( $pre. $cdr->dst ) unless $cdr->dst =~ /^$pre/; - }, - - # CALL TYPE / Possibly / Not sure if you need this to determine correct - # billing method ? - # DDD normal call (Direct Dial Dsomething? ="LD"?) - # TF Toll Free - # (toll free dst# should be sufficient to rate) - # DAT Directory AssisTance - # (dst# 411 "area code" should be sufficient to rate) - # DNS (Another sort of directory assistance?... only one record with - # "8195551212" in the dst#) - 'dcontext', #probably don't need... map to cdr_type? calltypenum? - - # DURATION Yes Units = seconds - 'billsec', #need to trim .00 ? - - # AMOUNT CHARGED No Will use Freeside rating and description name - sub { my( $cdr, $amount) = @_; - $amount =~ s/^\$//; - $cdr->upstream_price( $amount ); - }, - - ], - -); - -1; - -__END__ - -CHG TYPE (unused) -ACCOUNT # (unused) - -DATE startdate (+ CUST NO) -CUST NO (startdate time) - - Start of call (UNIX-style integer timestamp) - -BTN *src - Caller*ID number / Source number -ORIG CITY channel - Channel used -TERM # *dst - Destination extension -TERM CITY dstchannel - Destination channel if appropriate -WTN *charged_party - Service number to be billed -CODE *accountcode - CDR account number to use: account - -PROV/COUNTRY (used to prefix TERM # w/ 1 or 011) - -CALL TYPE dcontext - Destination context -DURATION *billsec - Total time call is up, in seconds -AMOUNT CHARGED *upstream_price - Wholesale price from upstream - diff --git a/FS/FS/cdr/broadsoft.pm b/FS/FS/cdr/broadsoft.pm deleted file mode 100644 index 423e96fcc..000000000 --- a/FS/FS/cdr/broadsoft.pm +++ /dev/null @@ -1,108 +0,0 @@ -package FS::cdr::broadsoft; - -use strict; -use base qw( FS::cdr ); -use vars qw( %info ); -use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker ); - -%info = ( - 'name' => 'Broadsoft', - 'weight' => 500, - 'header' => 1, #0 default, set to 1 to ignore the first line, or - # to higher numbers to ignore that number of lines - 'type' => 'csv', #csv (default), fixedlength or xls - 'sep_char' => ',', #for csv, defaults to , - 'disabled' => 0, #0 default, set to 1 to disable - - #listref of what to do with each field from the CDR, in order - 'import_fields' => [ - - skip(2), - sub { my($cdr, $data, $conf, $param) = @_; - $param->{skiprow} = 1 if lc($data) ne 'normal'; - '' }, # 3: type - - trim('accountcode'), # 4: userNumber - skip(2), - trim('src'), # 7: callingNumber - skip(1), - trim('dst'), # 9: calledNumber - - _cdr_date_parser_maker('startdate'), # 10: startTime - skip(1), - sub { my($cdr, $data) = @_; - $cdr->disposition( - lc($data) eq 'yes' ? - 'ANSWERED' : 'NO ANSWER') }, # 12: answerIndicator - _cdr_date_parser_maker('answerdate'), # 13: answerTime - _cdr_date_parser_maker('enddate'), # 14: releaseTime - - ], - -); - -sub trim { - my $fieldname = shift; - return sub { - my($cdr, $data) = @_; - $data =~ s/^\+1//; - $cdr->$fieldname($data); - '' - } -} - -sub skip { - map { undef } (1..$_[0]); -} - -1; - -__END__ - -list of freeside CDR fields, useful ones marked with * - - acctid - primary key - *[1] calldate - Call timestamp (SQL timestamp) - clid - Caller*ID with text -7 * src - Caller*ID number / Source number -9 * dst - Destination extension - dcontext - Destination context - channel - Channel used - dstchannel - Destination channel if appropriate - lastapp - Last application if appropriate - lastdata - Last application data -10 * startdate - Start of call (UNIX-style integer timestamp) -13 answerdate - Answer time of call (UNIX-style integer timestamp) -14 * enddate - End time of call (UNIX-style integer timestamp) - * duration - Total time in system, in seconds - * billsec - Total time call is up, in seconds -12 *[2] disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY - amaflags - What flags to use: BILL, IGNORE etc, specified on a per - channel basis like accountcode. -4 *[3] accountcode - CDR account number to use: account - uniqueid - Unique channel identifier - userfield - CDR user-defined field - cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8) - *[4] charged_party - Service number to be billed - upstream_currency - Wholesale currency from upstream - *[5] upstream_price - Wholesale price from upstream - upstream_rateplanid - Upstream rate plan ID - rated_price - Rated (or re-rated) price - distance - km (need units field?) - islocal - Local - 1, Non Local = 0 - *[6] calltypenum - Type of call - see FS::cdr_calltype - description - Description (cdr_type 7&8 only) (used for - cust_bill_pkg.itemdesc) - quantity - Number of items (cdr_type 7&8 only) - carrierid - Upstream Carrier ID (see FS::cdr_carrier) - upstream_rateid - Upstream Rate ID - svcnum - Link to customer service (see FS::cust_svc) - freesidestatus - NULL, done (or something) - -[1] Auto-populated from startdate if not present -[2] Package options available to ignore calls without a specific disposition -[3] When using 'cdr-charged_party-accountcode' config -[4] Auto-populated from src (normal calls) or dst (toll free calls) if not present -[5] When using 'upstream_simple' rating method. -[6] Set to usage class classnum when using pre-rated CDRs and usage class-based - taxation (local/intrastate/interstate/international) diff --git a/FS/FS/cdr/cia.pm b/FS/FS/cdr/cia.pm deleted file mode 100644 index 61343338a..000000000 --- a/FS/FS/cdr/cia.pm +++ /dev/null @@ -1,39 +0,0 @@ -package FS::cdr::cia; - -use strict; -use vars qw( @ISA %info ); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Client Instant Access', - 'weight' => 510, - 'header' => 1, - 'type' => 'csv', - 'sep_char' => "\t", - 'import_fields' => [ - skip(2), # Reseller Account Number, Confirmation Number - 'description', # Conference Name - skip(3), # Organization Name, Bill Code, Q&A Active - 'userfield', # Chairperson Name - skip(2), # Conference Start Time, Conference End Time - _cdr_date_parser_maker('startdate'), # Connect Time - _cdr_date_parser_maker('enddate'), # Disconnect Time - sub { my($cdr, $data, $conf, $param) = @_; - $cdr->duration($data); - $cdr->billsec( $data); - }, # Duration - skip(2), # Roundup Duration, User Name - 'dst', # DNIS - 'src', # ANI - skip(2), # Call Type, Toll Free, - skip(1), # Chair Conference Entry Code - 'accountcode', # Participant Conference Entry Code, - ], - -); - -sub skip { map {''} (1..$_[0]) } - -1; diff --git a/FS/FS/cdr/genband.pm b/FS/FS/cdr/genband.pm deleted file mode 100644 index 619d9085f..000000000 --- a/FS/FS/cdr/genband.pm +++ /dev/null @@ -1,120 +0,0 @@ -package FS::cdr::genband; - -use strict; -use vars qw(@ISA %info); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'GenBand (Tekelec)', #'Genband G6 (Tekelec T6000)', - 'weight' => 140, - 'type' => 'fixedlength', - 'fixedlength_format' => [qw( - Type:2:1:2 - Sequence:4:3:6 - OIDCall:30:7:36 - StartTime:19:37:55 - AnswerTime:19:56:74 - EndTime:19:75:93 - SourceName:30:94:123 - SourceEndName:30:124:153 - SourceCallerID:20:154:173 - SourceCallerName:30:174:203 - DestinationName:30:204:233 - DestinationEndName:30:234:263 - DestCallerID:20:264:283 - DestCallerIDInfo:30:284:313 - DialedDigits:30:314:343 - Billing:30:344:373 - AuthCode:30:374:403 - CallDirection:1:404:404 - ExtendedCall:1:405:405 - ExternalCall:1:406:406 - Duration:9:407:415 - SIPCallID:64:416:479 - IncomingDigits:30:480:509 - OutpulsedDigits:30:510:539 - CarrierIdentificationCode:4:540:543 - CompletionReason:4:544:547 - OriginationPartition:30:548:577 - DestinationPartition:30:578:607 - BilledSourceDID:20:608:627 - OriginalCall:30:628:657 - VideoCall:1:658:658 - )], - 'import_fields' => [ - sub {}, #Type:2:1:2 - sub {}, #Sequence:4:3:6 - 'uniqueid', #OIDCall:30:7:36 - _cdr_date_parser_maker('startdate'), #StartTime:19:37:55 - _cdr_date_parser_maker('answerdate'), #AnswerTime:19:56:74 - _cdr_date_parser_maker('enddate'), #EndTime:19:75:93 - sub {}, #SourceName:30:94:123 - 'channel', #SourceEndName:30:124:153 - 'src', #SourceCallerID:20:154:173 - 'clid', #SourceCallerName:30:174:203 - sub {}, #DestinationName:30:204:233 - 'dstchannel', #DestinationEndName:30:234:263 - 'dst', #DestCallerID:20:264:283 - sub {}, #DestCallerIDInfo:30:284:313 - sub {}, #DialedDigits:30:314:343 - sub {}, #Billing:30:344:373 - sub {}, #AuthCode:30:374:403 - sub {}, #CallDirection:1:404:404 - sub {}, #ExtendedCall:1:405:405 - sub {}, #ExternalCall:1:406:406 - sub { my( $cdr, $duration ) = @_; - $cdr->duration($duration); - $cdr->billsec($duration); }, #'duration', #Duration:9:407:415 - sub {}, #SIPCallID:64:416:479 - sub {}, #IncomingDigits:30:480:509 - sub {}, #OutpulsedDigits:30:510:539 - sub {}, #CarrierIdentificationCode:4:540:543 - sub {}, #CompletionReason:4:544:547 - sub {}, #OriginationPartition:30:548:577 - sub {}, #DestinationPartition:30:578:607 - sub {}, #BilledSourceDID:20:608:627 - sub {}, #OriginalCall:30:628:657 - sub {}, #VideoCall:1:658:658 - ], -); -# acctid - primary key -# calldate - Call timestamp (SQL timestamp) -# clid - Caller*ID with text -# src - Caller*ID number / Source number -# dst - Destination extension -# dcontext - Destination context -# channel - Channel used -# dstchannel - Destination channel if appropriate -# lastapp - Last application if appropriate -# lastdata - Last application data -# startdate - Start of call (UNIX-style integer timestamp) -# answerdate - Answer time of call (UNIX-style integer timestamp) -# enddate - End time of call (UNIX-style integer timestamp) -# duration - Total time in system, in seconds -# billsec - Total time call is up, in seconds -# disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY -# amaflags - What flags to use: BILL, IGNORE etc, specified on a per -# channel basis like accountcode. -# accountcode - CDR account number to use: account -# uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID) -# userfield - CDR user-defined field -# cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8) -# charged_party - Service number to be billed -# upstream_currency - Wholesale currency from upstream -# upstream_price - Wholesale price from upstream -# upstream_rateplanid - Upstream rate plan ID -# rated_price - Rated (or re-rated) price -# distance - km (need units field?) -# islocal - Local - 1, Non Local = 0 -# calltypenum - Type of call - see FS::cdr_calltype -# description - Description (cdr_type 7&8 only) (used for -# cust_bill_pkg.itemdesc) -# quantity - Number of items (cdr_type 7&8 only) -# carrierid - Upstream Carrier ID (see FS::cdr_carrier) -# upstream_rateid - Upstream Rate ID -# svcnum - Link to customer service (see FS::cust_svc) -# freesidestatus - NULL, done (or something) - -1; diff --git a/FS/FS/cdr/genband_meetme.pm b/FS/FS/cdr/genband_meetme.pm deleted file mode 100644 index d87dd8fbf..000000000 --- a/FS/FS/cdr/genband_meetme.pm +++ /dev/null @@ -1,17 +0,0 @@ -package FS::cdr::genband_meetme; - -use strict; -use vars qw(@ISA %info); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Genband (Tekelec) Meet-Me Conference', #'Genband G6 (Tekelec T6000) Meet-Me Conference Log Records', - 'weight' => 145, - 'disabled' => 1, - 'import_fields' => [ - ], -); - -1; diff --git a/FS/FS/cdr/indosoft.pm b/FS/FS/cdr/indosoft.pm deleted file mode 100644 index cb25089e3..000000000 --- a/FS/FS/cdr/indosoft.pm +++ /dev/null @@ -1,71 +0,0 @@ -package FS::cdr::indosoft; - -use strict; -use base qw( FS::cdr ); -use vars qw( %info ); -use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker ); - -%info = ( - 'name' => 'Indosoft Conference Bridge', - 'weight' => 300, - 'header' => 1, - 'type' => 'csv', - - #listref of what to do with each field from the CDR, in order - 'import_fields' => [ - - #cdr_id - 'uniqueid', - - #connect_time - _cdr_date_parser_maker( ['startdate', 'answerdate' ] ), - - #disconnect_time - _cdr_date_parser_maker('enddate'), - - #account_id - 'accountcode', - - #conference_id - 'userfield', - - #client_id - 'charged_party', - - #pin_used - 'dcontext', - - #channel - 'channel', - - #clid - #'src', - sub { my($cdr, $clid) = @_; - $cdr->clid( $clid ); #because they called it 'clid' explicitly - $cdr->src( $clid ); - }, - - #dnis - 'dst', - - #call_status - 'disposition', - - #conf_billing_code - 'lastapp', #arbitrary - - #participant_id - 'lastdata', #arbitrary - - #codr_id - 'dstchannel', #arbitrary - - #call_type - 'description', - - ], - -); - -1; - diff --git a/FS/FS/cdr/infinite.pm b/FS/FS/cdr/infinite.pm deleted file mode 100644 index 90560c8c7..000000000 --- a/FS/FS/cdr/infinite.pm +++ /dev/null @@ -1,41 +0,0 @@ -package FS::cdr::infinite; - -use strict; -use vars qw( @ISA %info ); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Infinite Conferencing', - 'weight' => 520, - 'header' => 1, - 'type' => 'csv', - 'sep_char' => ',', - 'import_fields' => [ - 'uniqueid', # billid - skip(3), # confid, invoicenum, acctgrpid - 'accountcode', # accountid ("Room Confirmation Number") - skip(2), # billingcode ("Room Billingcode"), confname - skip(1), # participant_type - 'startdate', # starttime_t - skip(2), # startdate, starttime - sub { my($cdr, $data, $conf, $param) = @_; - $cdr->duration($data * 60); - $cdr->billsec( $data * 60); - }, # minutes - 'dst', # dnis - 'src', # ani - skip(8), # calltype, calltype_text, confstart_t, confstartdate, - # confstarttime, confminutes, conflegs, ppm - 'upstream_price', # callcost - skip(13), # confcost, rppm, rcallcost, rconfcost, - # auxdata[1..4], ldval, sysname, username, cec, pec - 'userfield', # unnamed field - ], - -); - -sub skip { map {''} (1..$_[0]) } - -1; diff --git a/FS/FS/cdr/netcentrex.pm b/FS/FS/cdr/netcentrex.pm deleted file mode 100644 index a434d5d5f..000000000 --- a/FS/FS/cdr/netcentrex.pm +++ /dev/null @@ -1,783 +0,0 @@ -package FS::cdr::netcentrex; - -use strict; -use vars qw(@ISA %info); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -#close enough http://wiki.freeswitch.org/wiki/Hangup_causes -#my %disposition = ( -# 16 => 'ANSWERED', -# 17 => 'BUSY', -# 18 => 'NO USER RESPONSE', -# 19 => 'NO ANSWER', -# 156 => '??' #??? -#); - -%info = ( - 'name' => 'NetCentrex', - 'weight' => 150, - 'type' => 'csv', - 'sep_char' => ';', - 'import_fields' => [ - '', #00 SU Identifier - '', #01 SU IP Address - '', #02 Conference ID - '', #03 Call ID - '', #04 Leg number (all 0) - _cdr_date_parser_maker('startdate'), #05 Authorize timestamp - _cdr_date_parser_maker('answerdate'), #06 Start timestamp - sub { my( $cdr, $duration ) = @_; #07 Duration - $cdr->duration($duration); - $cdr->billsec( $duration); - }, - _e164_parser_maker('src', 'charged_party'), #08 Caller - _e164_parser_maker('dcontext', 'dst', 'norewrite_pivotonly'=>1) ,#09 Callee - 'channel', #10 Source IP - 'dstchannel', #11 Destination IP - 'userfield', #12 selector Tag - '', #13 *service Tag - '', #14 *announcement Tag - '', #15 *route Table Tag - '', #16 vTrunkGroup Tag - '', #17 vTrunk Tag XXX ? another userfield? - '', #18 *termination Tag - '', #19 *location group Tag - '', #20 *GK Originating IP - '', #21 *GK Terminating IP - '', #22 *GK Originating Domain - '', #23 *GK Terminating Domain - '', #24 Malicious Call (all 0) - '', #25 Service (all 0) - 'disposition', #26 Termination Cause 16/17/18/156 - '', #27 Simulation Call (all 0) supposedly don't bill 1 - '', #28 Type (all C) - _cdr_date_parser_maker('enddate'), #29 ReleaseTimeStamp - #seems empty from here in sampes... - '', #30 - '', #31 - '', #32 - '', #33 - '', #34 - '', #35 - '', #36 - '', #37 - '', #38 - '', #39 - '', #40 - '', #41 - '', #42 - '', #43 - '', #44 - '', #45 - '', #46 - '', #47 - '', #48 - '', #49 - '', #50 - - # * empty - ], - -); - -sub _e164_parser_maker { - my( $field, $pivot_field, %opt ) = @_; - return sub { - my( $cdr, $e164 ) = @_; - my( $pivot, $number ) = _e164_parse($e164); - if ( $opt{'norewrite_pivotonly'} && ! $pivot ) { - $cdr->$pivot_field( $number ); - } else { - $cdr->$field( $number ); - $cdr->$pivot_field( $pivot ); - } - }; -} - -sub _e164_parse { - my $e164 = shift; - - $e164 =~ s/^e164://; - - my ($pivot, $number); - if ( $e164 =~ /^O(\d+)$/ ) { - $pivot = ''; #? - $number = $1; - } elsif ( $e164 =~ /^000000(\d+)$/ ) { - $pivot = ''; - $number = $1; - } elsif ( $e164 =~ /^(1\d{5})(\d+)$/ ) { - $pivot = $1; - $number = $2; - } else { - $pivot = ''; - $number = $e164; #unparsable... - } - - ( $pivot, $number ); -} - -1; - -=pod - - calldate - Call timestamp (SQL timestamp) - clid - Caller*ID with text - src - Caller*ID number / Source number - dst - Destination extension - dcontext - Destination context - channel - Channel used - dstchannel - Destination channel if appropriate - lastapp - Last application if appropriate - lastdata - Last application data - startdate - Start of call (UNIX-style integer timestamp) - answerdate - Answer time of call (UNIX-style integer timestamp) - enddate - End time of call (UNIX-style integer timestamp) - duration - Total time in system, in seconds - billsec - Total time call is up, in seconds - disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY - amaflags - What flags to use: BILL, IGNORE etc, specified on a per - channel basis like accountcode. - accountcode - CDR account number to use: account - uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID) - userfield - CDR user-defined field - cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8) - charged_party - Service number to be billed - upstream_currency - Wholesale currency from upstream - upstream_price - Wholesale price from upstream - upstream_rateplanid - Upstream rate plan ID - rated_price - Rated (or re-rated) price - distance - km (need units field?) - islocal - Local - 1, Non Local = 0 - calltypenum - Type of call - see FS::cdr_calltype - description - Description (cdr_type 7&8 only) (used for - cust_bill_pkg.itemdesc) - quantity - Number of items (cdr_type 7&8 only) - carrierid - Upstream Carrier ID (see FS::cdr_carrier) - upstream_rateid - Upstream Rate ID - svcnum - Link to customer service (see FS::cust_svc) - freesidestatus - NULL, done (or something) - cdrbatch - -No. Field Type/Length Format / Remarks Description Example -00 SU Identifier String This field is never empty. SU Identifier (as defined by su- su01 - <= 16 chars core.ini/[SU]/SUInstance key at SU - 192.168.121.1 - initialization). - By default, the SUInstance is set to - a string that represents the SU - private IP address. -01 SU IP address String ipv4:xx.xx.xx.xx<:port> SU IP address (and ASM port) as ipv4:213.56.136.29: 2518 - <= 26 chars provided by su- - This field is never empty. - crouting.ini/[crRouting]/localASMa - ddress key. -02 Conference ID String When [CDR_FIELDS] Unique call session identifier Advised format - <= 64 chars ReadlIDFormat is set to 1 in provided by the SU, as received in (ReadlIDFormat=1): - ncx-cdr-wrapper.ini (advised call initiation message (H.225 910a4b12 cd67d93f - format): conferenceID field in Setup or 4300abd2 cc10a0a0 - ARQ). - 4x4 bytes as an hexadecimal RealIDFormat=0: - string; double words are - 12.123.54.125.67.235.255.2 - space-separated - 31.9.12.4.3.7.19.245.65 - When [CDR_FIELDS] - ReadlIDFormat is set to 0 in - ncx-cdr-wrapper.ini: - 16xdecimal notation of a 1- - byte number (0..255), dot- - separated. - This field is never empty. -03 Call ID String When [CDR_FIELDS] Call identifier provided by the ASM Advised format - <= 64 chars ReadlIDFormat is set to 1 in in the SU (it can be the CallID or (ReadlIDFormat=1): - ncx-cdr-wrapper.ini (advised the RealCallID according to what is 910a4b12 cd67d93f - format): set in the ncx-cdr-wrapper.ini 4300abd2 cc10a0a0 - UseRealCallID field). It is received - 4x4 bytes as an hexadecimal RealIDFormat=0: - in call initiation message (H.225 - string; double words are - callID field in Setup or ARQ). 12.123.54.125.67.235.255.2 - space-separated - 31.9.12.4.3.7.19.245.65 - When [CDR_FIELDS] - ReadlIDFormat is set to 0 in - ncx-cdr-wrapper.ini: - 16xdecimal notation of a 1- - byte number (0..255), dot- - separated. - This field may be empty if no - H.225 callID is present in - ARQ. -04 Leg number Integer Always set to 0 when the call Call attempt index, starting at 0. 0 - ~ 1 char is not deflected. Incremented whenever a call leg - to a new destination is created. - This field is never empty. - A single call without any call - forward service will only have 1 - CDR line, whose Leg number is set - to 0. - If a call is redirected (on - CFU/CFB/CNFR), it will generate a - second CDR line, leg number 1. - The leg number is then - incremented on each subsequent - redirection. - -05 Authorize Long It can have two formats as Authorize date and time of the call 1039189431 - timestamp 10 chars given in the ncx-cdr- leg => enable to have a date and - wrapper.ini by the time if a call is not connected. - TimestampFormat field. UTC. - If TimestampFormat is set to This is the ARQ or SETUP or - 0, the result string INVITE reception timestamp for - corresponds to the "epoch" the first call leg. For next tickets, - time, the number of elapsed this is the call deflection processing - seconds since 1970/01/01 start time. Thus, this value may - 00:00:00 (UTC) vary in tickets related to a - complete call. - If TimestampFormat is set to - 1, the result string is 20 chars - in length (format: YYYY-MM- - DD HH:MM:SS) - NOTE: if you choose - TimestampFormat = 0 you - can have the tenth of second - (UseTenthOfSecond = 1) or - the micro second - (UseMicroSecond = 1) - NOTE: you can hide - timestamp equal to 0 (or - 1970/01/01 00:00:00) with - the key HideNullTimestamp - set to 1. - This field is never empty. -06 Start timestamp Long It can have two formats as Starting date and time of the call 1039189431 - 10 chars given in the ncx-cdr- leg. UTC. - wrapper.ini by the - This is the CONNECT or OK (after - TimestampFormat field. - INVITE) reception timestamp. It is - If TimestampFormat is set to set to the same value for all tickets - 0, the result string related to a call. - corresponds to the "epoch" - time, the number of elapsed - seconds since 1970/01/01 - 00:00:00 (UTC) - If TimestampFormat is set to - 1, the result string is 20 chars - in length (format: YYYY-MM- - DD HH:MM:SS) - 0 (or 1970/01/01 00:00:00) - means the connection was not - established for this call leg. - NOTE: if you choose - TimestampFormat = 0 you - can have the tenth of second - (UseTenthOfSecond = 1) or - the micro second - (UseMicroSecond = 1) - NOTE: you can hide - timestamp equal to 0 (or - 1970/01/01 00:00:00) with - the key HideNullTimestamp - set to 1. - This field may be empty if the - call is not connected. -07 Duration Long In seconds (0 means the Duration of the call leg (in 6 - <= 10 chars connection was not seconds), after the connection was - established for this call leg). established. - NOTE: you can have the tenth Set to 0 for SIP NOTIFICATION - of second (UseTenthOfSecond and SIP MESSAGE reports. - = 1) or the micro second - (UseMicroSecond = 1) - This field is never empty. -08 Caller String e164:[number] or h323:[alias] Main Source Alias in pivot format e164:0010033575 - or email:[alias] (provided by the ASM) - <= 128 chars - This field may be empty if the If pivot format cannot be - Caller pivot alias cannot be computed then the main source - computed. alias is presented in originating - format and the "O" char is inserted - See Use Cases section for - at the beginning of the alias or - possible cases. - number. - NOTE: the phone-context and - trunk-context are set if present. -09 Callee String e164:[number] or h323:[alias] E.164 Called Party Number alias or e164:0010033762 - or email:[alias] H323 destination ID in pivot - <= 128 chars - format (provided by the ASM) - This field may be empty if the - Callee pivot alias cannot be If pivot format cannot be - computed. computed then the originating - format is presented and the "O" - char is inserted at the beginning of - the alias or number. - NOTE: the phone-context and - trunk-context are set if present. -10 Source IP String ipv4:xx.xx.xx.xx<:port> If ncx-cdr-wrapper.ini/useFullIP = ipv4:192.168.1.2:34123 - 0: - <= 26 chars This field may be empty if the - Source IP cannot be retrieved Source IP address of the caller, as - in IP message mode. used for IP filtering (thus, may be - either Packet IP address or - CallSignalAddress, depending on - su- - crouting.ini/[defaultH323Parameter - s]/ipFiltering key - It can also be changed by the - selector "extended actions" - parameter. See "selector extended - actions" dedicated documentation - for further information. - If ncx-cdr-wrapper.ini/useFullIP = - 1: - Source IP packet address for the - call leg -11 Destination IP String ipv4:xx.xx.xx.xx<:port> If ncx-cdr-wrapper.ini/useFullIP = ipv4:213.56.162.17 - 0: - <= 26 chars This field may be empty if - destination IP cannot be Destination IP signaling address - resolved. for the call leg - If ncx-cdr-wrapper.ini/useFullIP = - 1: - Destination IP packet address for - the call leg - NOTE: Can be different from the - signaling address when routing - through a proxy group. This field - refers to the proxy IP address. - Otherwise IP signaling address and - IP packet address are the same. -12 selector Tag String This field is empty for non Extensible tag. See extension tag in=33231412345,vp=165,si - <= 199 chars Business Services managed format below. =123 tz=Europe/Berlin, - sources and for Sites with no - Selector Tag placed on the selector - PSTN ranges allocated. - for this call - See [ref: 2] and [ref: 3] for further - 2 2 - information. -13 service Tag Full alphanumeric This field is empty for now. Service Tag placed on the selector - string or on the vTrunkGroup for this call. - See [ref: 2] and [ref: 3] for further - 2 - information. -14 announcement Full alphanumeric This field is empty for now. Announcement Tag placed on the - Tag string selector, routeTable or - vTrunkGroup for this call. - See [ref: 2]and [ref: 3] for further - 2 - information. -15 route Table Tag Full alphanumeric This field is empty for now. Route table Tag placed on the - string route table for this call. - See [ref: 2] and [ref: 3] for further - 2 2 - information. -16 vTrunkGroup Full alphanumeric This field is empty for now. vTrunkGroupTag placed on the - Tag string vTrunkGroup for this call. - See [ref: 2] and [ref: 3] for further - 2 - information. -17 vTrunk Tag String This field is empty for non Extensible tag. See extension tag in=33156341289,vp=4232,s - <= 199 chars Business Services managed format below. i=132,tz=Europe/Paris - destinations and for Sites with - vTrunk Tag placed on the vTrunk - no PSTN ranges allocated. - for this call. - See [ref: 2] and [ref: 3] for further - 2 2 - information. -18 termination Tag Full alphanumeric This field is empty for now. Termination Tag placed on the - string Termination for this call. - See [ref: 2] and [ref: 3] for further - 2 2 - information. -19 location group Full alphanumeric This field is empty for now. location group Tag placed on the - Tag string selector for this call. - See [ref: 2] and [ref: 3] for further - 2 2 - information. -20 GK Originating Full alphanumeric This field is empty for now. Parameter provided by the ASM in - IP string the SU (reserved for future usage). -21 GK Terminating Full alphanumeric This field is empty for now. Parameter provided by the ASM in - IP string the SU (reserved for future usage). -22 GK Originating Full alphanumeric This field is empty for now. Parameter provided by the ASM in - Domain string the SU (reserved for future usage). -23 GK Terminating Full alphanumeric This field is empty for now. Parameter provided by the ASM in - Domain string the SU (reserved for future usage). -24 Malicious Call Boolean 0/1 Indicate if a call is malicious or 0 - not. All calls to a specific called - 1 char - party will be tagged as malicious - when the malicious feature has - been activated. -25 Service Long 0..31 Bit mask for activated services for 6: at least one - <= 3 chars this call. TECHNOLOGY and one - This field is never empty. - REMOVE service objects - This is a combination between the - have been used during - following values: - routing process - 1: if at least one CLIR service - 10: at least one BASIC- - object has been used during - XACTION and one REMOVE - routing process - service objects have been - 2: if at least one REMOVE service used during routing process - object has been used during - routing process - 4: if at least one TECHNOLOGY - service object has been used - during routing process - 8: if at least one BASIC-XACTION - service object has been used - during routing process - 16: if at least one SUBSTITUTION - service object has been used - during routing process - This is independent from the su- - crouting.ini configuration file and - in particular from the SPE - activation. -26 Termination Long Causes in the range [1-127] Cause of the call termination. 16 - Cause <= 3 chars are standard Q.850 causes - Causes >= 128 are specific - Comverse extension causes. - See [ref. 5] for possible values - and meanings. - This field is never empty. -27 Simulation Call Boolean 0/1 Indicates if a call is a simulation 0 - 1 char call or not. - This field is never empty. - SIMULATION CALLS MUST NOT BE - BILLED. - Simulation calls can only be - generated through the Telnet - interface (tests and diagnostic - only). -28 Type One character Optional field depending on Type of CDR: C - the UseType entry in ncx-cdr- - 1 char - Call ('C'): for INVITE and SETUP - wrapper.ini. If set to 1, a - value in this field will be - Notification ('N') for SIP - always printed: 'C' by default. NOTIFICATION - 'C', 'N' or 'M'. - Message ('M') for SIP MESSAGE - This field is never empty. -29 ReleaseTimeSta Long Optional field depending of Release date of the leg. 1039189431 - mp 10 chars the UseReleaseTimeStamp - entry in ncx-cdr-wrapper.ini. - It can have two formats as - given in the ncx-cdr- - wrapper.ini by the - TimestampFormat field. - If TimestampFormat is set to - 0, the result string - corresponds to the "epoch" - time, the number of elapsed - seconds since 1970/01/01 - 00:00:00 (UTC) - If TimestampFormat is set to - 1, the result string is 20 chars - in length (format: YYYY-MM- - DD HH:MM:SS) - NOTE: if you choose - TimestampFormat = 0 you - can have the tenth of second - (UseTenthOfSecond = 1) or - the micro second - (UseMicroSecond = 1) - NOTE: you can hide - timestamp equal to 0 (or - 1970/01/01 00:00:00) with - the key HideNullTimestamp - set to 1. - This field is empty when no - CRR message is received and - therefore it will be empty for - the CDR describing presence - message (SIP NOTIFY and SIP - MESSAGE). It is also empty - when the CDR is closed by the - AMU (e.g. if the SU is - detected as DOWN). - In all other cases, this field is - never empty -30 cgIdentity Tag Full alphanumeric Optional: this field is filled if Extensible tag for Calling Party. pu=33231345123,pr=23 - string usecgidentitytag is set to 1 in See extension tag format below. - <= 132 chars ncx-cdr-wrapper.ini. - This field is empty for non - Business Services/class V - managed sources. - The content of this field differs - between BS and MyCall - solutions. -31 cdIdentity Tag Full alphanumeric Optional: this field is filled if Extensible tag for Called Party. See pr=1111,bi=ADMIN - string usecdidentitytag is set to 1 in extension tag format below. - <= 132 chars ncx-cdr-wrapper.ini - This field is empty for non - Business Services/class V - managed destinations. - The content of this field differs - between BS and MyCall - solutions. -32 Originating String Optional: this field is filled if E.164 Main Source alias or H323 e164:0010033575 - Caller <= 128 chars useoriginatingcaller is set to 1 source ID in originating format (as - in ncx-cdr-wrapper.ini received from the network) - e164:[number] or h323:[alias] The Main Source alias is computed - or email:[alias] according to su-core.ini - configuration. - NOTE: the phone-context and - trunk-context are set if present. -33 Originating String Optional: this field is filled if E.164 Main Destination alias or e164:0010033762 - Callee <= 128 chars useoriginatingcallee is set to 1 H323 destination ID in originating - in ncx-cdr-wrapper.ini format (as received from the - network) - e164:[number] or h323:[alias] - or email:[alias] The Main Destination alias is - computed according to su-core.ini - configuration. - NOTE: the phone-context and - trunk-context are set if present. -34 Terminating String Optional: this field is filled if E.164 Calling Party Number alias or e164:0010033575 - Caller <= 128 chars useterminatingcaller is set to 1 H323 source ID in terminating - in ncx-cdr-wrapper.ini format (as provided to the - network). - e164:[number] or h323:[alias] - or email:[alias] NOTE: the phone-context and - trunk-context are set if present. -35 Terminating String Optional: this field is filled if E.164 Called Party Number alias or e164:0010033762 - Callee <= 128 chars useterminatingcallee is set to H323 destination ID in terminating - 1 in ncx-cdr-wrapper.ini. format (as provided to the - network). - e164:[number] or h323:[alias] - or email:[alias] NOTE: the phone-context and - trunk-context are set if present. - This field may be empty if no - terminating destination aliases - can be computed by the CRE - (missing vtrunk transformation - or unable to found a vtrunk - for whatever routing reason), - or if the pivot to terminating - destination alias - transformation leads to an - empty alias. -36 Network Long Optional: this field is filled if For H.323 the network timestamp 1039189431 - Timestamp 10 chars usenetworkcompletiontimesta is measured at the first Progress or - mp is set to 1 in ncx-cdr- ALERT or CONNECT received by - wrapper.ini. the CCS for direct call. - For redirected call, the network - It can have two formats as - timestamp is measured by the - given in the ncx-cdr- - CCS at the redirection decision - wrapper.ini by the - point, - TimestampFormat field. - NOTE: For H.323 calls, the tcp-ack - If TimestampFormat is set to - of the outgoing TCP connection is - 0, the result string - not considered in the measure of - corresponds to the "epoch" - network timestamp - time, the number of elapsed - seconds since 1970/01/01 For SIP the network timestamp is - 00:00:00 (UTC) measured at the first SESSION - PROGRESS or RINGING or OK - If TimestampFormat is set to - received by the CCS for direct call. - 1, the result string is 20 chars - in length (format: YYYY-MM- The network timestamp is - DD HH:MM:SS) measured at the redirection - decision point for redirected call. - NOTE: if you choose - TimestampFormat = 0 you - can have the tenth of second - (UseTenthOfSecond = 1) or - the micro second - (UseMicroSecond = 1) - NOTE: you can hide - timestamp equal to 0 (or - 1970/01/01 00:00:00) with - the key HideNullTimestamp - set to 1. - This field may be empty if the - callee does not answer. -37 Targeted Integer Optional: this field is filled if Provides information on the 12 - adaptor UseTargetedAdaptors is set to adaptor that has been used: "1" - <= 2 chars - 1 in ncx-cdr-wrapper.ini. for adaptor1, "2" for adaptor2 and - "12" for adaptor1 and adaptor2 - "1", "2" or "12" - See the amu-core.ini file section - for further details on adaptors - definition. -38 Adaptor1 errors String Optional: this field is filled if Report errors on adaptor1 at the cra,crr - UseAdaptor1Errors is set to 1 adaptor API level. - <= 15 chars - in ncx-cdr-wrapper.ini. - "nca" (error on the new call - authorize) - "cra" (error on the call re- - authorize) - "ncr" (error on the new call - report) - "crr" (error on the call release - report) - When several errors occurred, - comma separated notation will - be used. - Empty when no error has - been detected. -39 Source signaling String Optional: this field is filled in Source IP signaling address for the ipv4:192.168.1.2:34123 - IP only if useFullIP is set to 1 in call leg. - <= 26 chars - the ncx-cdr-wrapper.ini file. - It can be changed by the selector - ipv4:xx.xx.xx.xx<:port> "extended actions" parameter. See - "selector extended actions" - This field may be empty if the - dedicated documentation for - Source IP cannot be retrieved - further information. - in IP message mode. -40 Destination String Optional: this fields is filled in Destination IP signaling address ipv4:213.56.162.17 - signaling IP only if useFullIP is set to 1 in for the call leg - <= 26 chars - ncx-cdr-wrapper.ini file. - ipv4:xx.xx.xx.xx<:port>, can - be empty if destination IP - cannot be resolved. -41 Source point Unsigned integer Optional: this field is filled in SS7 point code, node identifier 1234 - code only if usePC is set to 1 in the - <= 5 chars - ncx-cdr-wrapper.ini file. - SIP: FROM header [TG-TEL]: - PC is Encoded in the trunk- - group part of a "tel" URI - extension (see also RFC - 3966). - H.323: H.225/circuitInfo: - Encoded in an - sourceCircuitID.cic.pointCode. -42 Destination point Unsigned integer Optional: this field is filled in SS7 point code, node identifier 1234 - code only if usePC is set to 1 in the - <= 5 chars - ncx-cdr-wrapper.ini file. - SIP: TO header [TG-TEL]: PC - is encoded in the trunk-group - part of a "tel" URI extension - (see also RFC 3966). - H.323: H.225/circuitInfo: - Encoded in a - destinationCircuitID.cic.pointC - ode. -43 Origination tag Full alphanumeric Optional: this field is filled in Origination tag placed on the crr=...,poi=... - string only if useOriginationTag is origination for this call. - set to 1 in the ncx-cdr- - wrapper.ini file. -44 Proxy group tag Full alphanumeric Optional: this field is filled in Proxy group Tag placed on the - string only if useProxyGroupTag is proxy group for this call. - set to 1 in the ncx-cdr- - wrapper.ini file. - This field is empty for now. -45 Advice of Charge String Optional: this field only is filled AOC received. rend=10.2,unit=EURO - in if UseAoc is set to 1 in ncx- - <= 50 chars cdr-wrapper.ini file. Available with CCS 3.8.4. - This field may be empty if - AOC service is not used or if - no AOC value is available. - =,unit= - with: - 1. (max length: - 7 chars): - Received AOC-D: 'rduring' - Received AOC-E, 'rend' - Other AOC types are not yet - supported by the su-core and - therefore are ignored. - 2. (max length: - 14 chars): - The amount is decoded from - the received AOC-D or AOC-E. - This value is mandatory in an - AOC. - 3. unit= (max length: - 15 chars): - The unit string is the decoded - unit value in the received - AOC-D or AOC-E. This value is - mandatory in an AOC. -46 Routing Context String Optional Routing context of the leg. basic - <= 5 chars 3 possible values: For IMS calls, routing context has - the value "orig" or "term". - - basic Otherwise, it is set to "basic". - - orig - Dependencies: - - term - - amu-core-4.8.0 - - adaptor-generic-cdr- - 1.8.0 - - ncx-cdr-wrapper-1.8.0 -47 Originating String Optional: this field is filled if E164 Main Source alias or H323 e164:33762 - Original Caller <= 128 chars useoriginatingoriginalcaller is source ID in originating format (as - set to 1 in ncx-cdr- received from the network) of the - wrapper.ini. original caller. - e164:[number] or h323:[alias] The main source alias is computed - or email:[alias] according to su-core.ini - configuration. - NOTE: the phone-context and - trunk-context are set if present. - Dependencies: - - amu-core-4.10.0 - - adaptor-generic-cdr- - 1.10.0 - - ncx-cdr-wrapper-1.10.0 -48 Pivot Original String Optional: this field is filled if E164 Main Source alias or H323 E164:0010033762 - Caller <= 128 chars usepivotoriginalcaller is set to source ID in pivot format (as - 1 in ncx-cdr-wrapper.ini. received from the network) of the - original caller - e164:[number] or h323:[alias] - or email:[alias] They are sent if present by SU if - su- - crouting.ini/[compatibility]/aliasRe - porting is 5_0_0 or greater - NOTE: the phone-context and - trunk-context are set if present. - Dependencies: - - amu-core-4.10.0 - - adaptor-generic-cdr- - 1.10.0 - - ncx-cdr-wrapper-1.10.0 -49 Terminating String Optional: this field is filled if E164 Main Source alias or H323 E164:0010033762 - Original Caller <= 128 chars useterminatingoriginalcaller is source ID in terminating format (as - set to 1 in ncx-cdr- received from the network) of the - wrapper.ini. original caller. - e164:[number] or h323:[alias] They are sent if present by SU if - or email:[alias] su- - crouting.ini/[compatibility]/aliasRe - porting is 5_0_0 or greater - NOTE: the phone-context and - trunk-context are set if present. - Dependencies: - - amu-core-4.10.0 - - adaptor-generic-cdr- - 1.10.0 - - ncx-cdr-wrapper-1.10.0 -50 Pivotclir Boolean Optional: this field is filled if Pivot CLIR calculated with caller clir=0 - UsePivotClir is set to 1 in ncx- information. - 6 chars cdr-wrapper.ini. - Dependencies: - 0 means that Calling Line - Identification is showed. - amu-core-4.12.0 - 1 means that Calling Line - adaptor-generic-cdr- - Identification is hidden. 1.12.0 - - ncx-cdr-wrapper-1.12.0 - diff --git a/FS/FS/cdr/nextone.pm b/FS/FS/cdr/nextone.pm deleted file mode 100644 index 22e6e86ed..000000000 --- a/FS/FS/cdr/nextone.pm +++ /dev/null @@ -1,26 +0,0 @@ -package FS::cdr::nextone; - -use strict; -use vars qw(@ISA %info); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Nextone', - 'weight' => 200, - 'header' => 1, - 'import_fields' => [ - 'userfield', #CallZoneData ???userfield - 'channel', #OrigGw - 'dstchannel', #TermGw - sub { my( $cdr, $duration ) = @_; - $cdr->duration($duration); - $cdr->billsec($duration); }, #Duration - 'dst', #CallDTMF - 'src', #Ani - 'startdate', #DateTimeInt - ], -); - -1; diff --git a/FS/FS/cdr/openser.pm b/FS/FS/cdr/openser.pm deleted file mode 100644 index 87fb82251..000000000 --- a/FS/FS/cdr/openser.pm +++ /dev/null @@ -1,24 +0,0 @@ -package FS::cdr::openser; - -use strict; -use vars qw(@ISA %info); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'OpenSER', - 'weight' => 15, - 'header' => 1, - 'import_fields' => [ - _cdr_date_parser_maker('startdate'), - _cdr_date_parser_maker('enddate'), - 'src', - 'dst', - 'duration', - 'channel', - 'dstchannel', - ], -); - -1; diff --git a/FS/FS/cdr/sansay.pm b/FS/FS/cdr/sansay.pm deleted file mode 100644 index 8087c570e..000000000 --- a/FS/FS/cdr/sansay.pm +++ /dev/null @@ -1,408 +0,0 @@ -package FS::cdr::sansay; - -use strict; -use base qw( FS::cdr ); -use vars qw( %info ); -use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker ); - -%info = ( - 'name' => 'Sansay VSX', - 'weight' => 135, - 'header' => 0, #0 default, set to 1 to ignore the first line, or - # to higher numbers to ignore that number of lines - 'type' => 'csv', #csv (default), fixedlength or xls - 'sep_char' => ';', #for csv, defaults to , - 'disabled' => 0, #0 default, set to 1 to disable - - - #listref of what to do with each field from the CDR, in order - 'import_fields' => [ - - # "Header" (I do not think this means what you think it means) - #002452502;V1.10;R; - - # Record Sequence Number 9 Unique identification of this record - 'uniqueid', - - '', #Version Number 5 Format version number of records to follow - # "V1.10" - '', #Record Type 1 Type of CDR being generated - # R ­ Normal CDR record, A - Audit - - # "Body" - #WithMedia;181-1071459514@192.188.0.28;0001;Mon Dec 15 11:38:34 2003;Mon Dec 15 11:38:41 2003;Mon Dec 15 11:38:48 2003;480;EndedByRemoteUser;3;T;000200;H323;;192.188.0.38;9001;192.188.0.28;f0faff54-2e6c-11d8-8c4b-bd4d562c2265;192.188.0.38;18044;192.188.0.28;10756;G.729b;240;460;6066;14060;0;0;0;000200;H323;;192.188.0.28;8811;192.188.0.38;e83af3d3-1d2d-d811-9f98-003048424934;192.188.0.38;19236;192.188.0.28;10758;G.729b;460;240;14060;6066;0;0;0;F;9001;305;2;15;305000;00000011 44934567 45231267 2300BCC0;8587542200; - - '', #ConnectionType 16 Type of connection : Media or No Media - '', #SessionID 32 Unique ID assigned to the call by - # SSM subsystem - '', #XXX #Release Cause 4 2.4 Internal process Release Cause - - #Cause Code Descriptions - #01 Normal answered call - #02 No Answer, tear down by originator - #03 No answer, tear down by the termination - #04 NORMAL_NO_ANSWER, tear down by - # system - #402 Service Not Available - #403 Termination capability un-compatible - #404 Outbound digit translation failed - #405 Termination reject for some other reasons - #406 Termination Route is blocked - #500 Originator is not in the Authorized list - # (source verification failed) - #501 Origination digit translation failed - #502 Origination direction is not bi-directional or - # inbound - #503 Origination is not in service state - #600 Max system call handling reached - #601 System reject call - #602 System outbound digit translation error - # (maybe invalid configuration) - #603 System inbound digit translation error - # (Maybe invalid configuration) - - - #Start Time of Date 32 Indicates Time of Date when the call - # entered the system - _cdr_date_parser_maker('startdate'), - - #Answer Time of Date 32 Indicates TOD when the call was - # answered - _cdr_date_parser_maker('answerdate'), - - #Release TOD 32 Indicates the TOD when the call was - # disconnected - _cdr_date_parser_maker('enddate'), - - #Minutes West of 32 Minutes West of Greenwich Mean - #Greenwich Mean Time Time. Used to calculate the time - # zone. - '', #XXX use this - - #Release Cause from 32 Release cause string from either H323 - #Protocol Stack or SIP protocol stack - #4. Release Cause String (Field #8 in CDR) - #- a string of text further identifying the teardown circumstance from terminating protocol message. - '', - - #Binary Value of Release 4 Binary value of the protocol release - #Cause from Protocol cause - #stack - # - #3. Release Cause from Stack ( Field # 9 in CDR) - #- an integer value based on the releasing dialogues protocol. - # a. For a H.323 call leg originated release it will be the real Q.931 value received from the far - # side. - #Some of the Q.931 release causes; - #3: No route to destination - #16; Normal Clearing - #17: User Busy - #19: NO Answer from User - #21; Call Rejected - #28: Address Incomplete - #34: No Circuit Channel Available - #.... - # b. For a SIP call leg originated release, it's a RFC 3261 release cause value received from the - # far side. - #The following is the list that VSX generated if certain event happen: - #"400 Parse Failed" - Malformed Message - #"405 Method Not Allowed" - Unsupported Method - #"480 Temporarily Unavailable" - Overload Throttle Rejection, Max Sessions - #Exceeded, Demo License Expired, Capacity Exceeded on Route, Radius Server Timeout - #"415 No valid codec" - No valid codec could be supported between origination and - #term call legs. - #"481 Transaction Does Not Exist" - Unknown Transaction or Dialog - #"487 Transaction Terminated" - Origination Cancel - #"488 ReInvite Rejected" - Relay of ReInvite was Rejected - #"504 Server Time-out" - Internal VSX Failure - #"500 Sequence Out of Order" - CSeq counter violation - # c. For a VSX system originated release, it an internal release cause for teardown. - #If the VSX initiates a call teardown, the following cause values and strings are written into the CDR: - #999, "Demo Licence Expired!" - #999, "VSX Capacity Exceeded" - #999, "VSX Operator Reset" - #999, "Route Rejected" - #999, "Radius Rejected" - #999, "Radius Access Timeout" - #999, "Gatekeeper Reject" - #999, "Enum Server Reject" - #999, "Enum Server Timeout" - #999, "DNS Server Reject" - #999, "DNS/GK Timeout" - #999, "Could not allocate media" - #999, "No Response to INVITE" - #999, "Ring No Answer Timeout" - #999, "200 OK Timeout" - #999, "Maximum Duration Exceeded" - #987, "Termination Capacity Exceeded" - #987, "Origination Capacity Exceeded" - #987, "Term CPS Capacity Exceeded" - #987, "Orig CPS Capacity Exceeded" - #987, "Max H323 Legs Exceeded" - '', - - #1st release dialogue 1 O: origination, T: termination - #2. 1st Release Dialogue ( Field #10 in CDR) - #- one character value identifying the side of the call that i - # ,,O ­ origination initiated the teardown. - # ,,T ­ termination initiated the teardown. - # ,,N ­ the VSX internally initiated the teardown. - '', - - #Trunk ID -- Origination 6 TrunkID for origination GW(resources) - 'accountcode', # right? # use cdr-charged_party-accountcode - - #VoIP Protocol - Origination 6 VoIP protocol for origination dialogue - '', - - #Origination Source Number 128 Source Number in Origination Dialogue - 'src', - - #Origination Source Host Name 128 FQDN or IP address for Source GW in Origination Dialogue - 'channel', - - #Origination Destination Number 128 Destination Number in Origination - #Dialogue - 'dst', - - #Origination Destination Host Name 128 FQDN or IP address for Destination - #GW in Origination Dialogue - 'dstchannel', - - #Origination Call ID 128 Unique ID for the origination dialogue(leg) - '', #'clid', #? that's not really the same call ID - - #Origination Remote 16 Remote Payload IP address for - # Payload IP origination dialogue - # Address - '', - - #Origination Remote 6 Remote Payload UDP address for - # Payload UDP origination dialogue - # Address - '', - - #Origination Local 16 Local(SG) Payload IP address for - # Payload IP origination dialogue - # Address - '', - - #Origination Local 6 Local(SG) Payload UDP address for - # Payload UDP origination dialogue - # Address - '', - - #Origination Codec List 128 Supported Codec list( separated by - # comma) for origination dialogue - '', - - #Origination Ingress 10 Number of Ingress( into Sansay - # Packets system) payload packets in - # origination dialogue - '', - - #Origination Egress 10 Number of Egress( out from Sansay - # Packets system) payload packets in - # origination dialogue - '', - - #Origination Ingress 10 Number of Ingress( into Sansay - # Octets system) payload octets in origination - # dialogue - '', - - #Origination Egress 10 Number of Egress( out from Sansay - # Octets system) payload octets in origination - # dialogue - '', - - #Origination Ingress 10 Number of Ingress( into Sansay - # Packet Loss system) payload packet loss in - # origination dialogue - '', - - #Origination Ingress 10 Average Ingress( into Sansay system) - # Delay payload packets delay ( in ms) in - # origination dialogue - '', - - #Origination Ingress 10 Average of Ingress( into Sansay - # Packet Jitter system) payload packet Jitter ( in ms) - # in origination dialogue - '', - - #Trunk ID -- Termination 6 Trunk ID for termination GW(resources) - 'carrierid', - - #VoIP Protocol - 6 VoIP protocol from termination GW - # Termination - '', - - #Termination Source 128 Source Number in Termination - # Number Dialogue - '', - - #Termination Source Host 128 FQDN or IP address for Source GW - # Name in Termination Dialogue - '', - - #Termination Destination 128 Destination Number in Termination - # Number Dialogue - '', - - #Termination Destination 128 FQDN or IP address for Destination - # Host Name GW in Termination Dialogue - '', - - #Termination Call ID 128 Unique ID for the termination - # dialogue(leg) - '', - - #Termination Remote 16 Remote Payload IP address for - # Payload IP termination dialogue - # Address - '', - - #Termination Remote 6 Remote Payload UDP address for - # Payload UDP termination dialogue - # Address - '', - - #Termination Local 16 Local(SG) Payload IP address for - # Payload IP termination dialogue - # Address - '', - - #Termination Local 6 Local(SG) Payload UDP address for - # Payload UDP termination dialogue - # Address - '', - - #Termination Codec List 128 Supported Codec list( separated by - # comma) for termination dialogue - '', - - #Termination Ingress 10 Number of Ingress( into Sansay - # Packets system) payload packets in - # termination dialogue - '', - - #Termination Egress 10 Number of Egress( out from Sansay - # Packets system) payload packets in - # termination dialogue - '', - - #Termination Ingress 10 Number of Ingress( into Sansay - # Octets system) payload octets in - # termination dialogue - '', - - #Termination Egress 10 Number of Egress( out from Sansay - # Octets system) payload octets in - # termination dialogue - '', - - #Termination Ingress 10 Number of Ingress( into Sansay - # Packet Loss system) payload packet loss in - # termination dialogue - '', - - #Termination Ingress 10 Average Ingress( into Sansay system) - # Delay payload packets delay ( in ms) in - # termination dialogue - '', - - #Termination Ingress 10 Average of Ingress( into Sansay - # Packet Jitter system) payload packet Jitter ( in ms) - # in termination dialogue - '', - - #Final Route Indication 1 F: Final Route Selection, - # I: Intermediate Route Attempts - '', - - #Routing Digits 64 Routing Digit (Digit after Inbound - # translation, before Outbound - # Translation). This may also be the - # LRN if LNP feature is enabled - '', - - #Call Duration in Second 6 Call Duration in Seconds. 0 if this is - # failed call - 'billsec', - - #Post Dial Delay in 6 Post dial delay (from call attempt to - # Seconds ring). 0 if this is failed call - '', - - #Ring Time in Second 6 Ring Time in Seconds. 0 if this is - # failed call - '', - - #Duration in milliseconds 10 Call duration in milliseconds. - '', - - #Conf ID 36 Unique Conference ID for this call in - # Cisco format - '', - - #RPID/ANI 32 Inbound Remote Party ID line or - # Proxy Asserted Identity if provided - 'clid', #? - - ], - -); - -1; - -__END__ - -list of freeside CDR fields, useful ones marked with * - -N/A acctid - primary key -FILLED_IN *[1] calldate - Call timestamp (SQL timestamp) -DONE clid - Caller*ID with text -DONE * src - Caller*ID number / Source number -DONE * dst - Destination extension - dcontext - Destination context -DONE channel - Channel used -DONE dstchannel - Destination channel if appropriate - lastapp - Last application if appropriate - lastdata - Last application data -DONE * startdate - Start of call (UNIX-style integer timestamp) -DONE answerdate - Answer time of call (UNIX-style integer timestamp) -DONE * enddate - End time of call (UNIX-style integer timestamp) -* duration - Total time in system, in seconds -DONE * billsec - Total time call is up, in seconds -*[2] disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY - amaflags - What flags to use: BILL, IGNORE etc, specified on a per - channel basis like accountcode. -DONE *[3] accountcode - CDR account number to use: account - uniqueid - Unique channel identifier - userfield - CDR user-defined field - cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8) -FILLED_IN *[4] charged_party - Service number to be billed - upstream_currency - Wholesale currency from upstream -*[5] upstream_price - Wholesale price from upstream - upstream_rateplanid - Upstream rate plan ID - rated_price - Rated (or re-rated) price - distance - km (need units field?) - islocal - Local - 1, Non Local = 0 -*[6] calltypenum - Type of call - see FS::cdr_calltype - description - Description (cdr_type 7&8 only) (used for - cust_bill_pkg.itemdesc) - quantity - Number of items (cdr_type 7&8 only) -DONE carrierid - Upstream Carrier ID (see FS::cdr_carrier) - upstream_rateid - Upstream Rate ID - svcnum - Link to customer service (see FS::cust_svc) - freesidestatus - NULL, done (or something) - -[1] Auto-populated from startdate if not present -[2] Package options available to ignore calls without a specific disposition -[3] When using 'cdr-charged_party-accountcode' config -[4] Auto-populated from src (normal calls) or dst (toll free calls) if not present -[5] When using 'upstream_simple' rating method. -[6] Set to usage class classnum when using pre-rated CDRs and usage class-based - taxation (local/intrastate/interstate/international) - diff --git a/FS/FS/cdr/simple.pm b/FS/FS/cdr/simple.pm deleted file mode 100644 index 197b0ebba..000000000 --- a/FS/FS/cdr/simple.pm +++ /dev/null @@ -1,52 +0,0 @@ -package FS::cdr::simple; - -use strict; -use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year ); -use Time::Local; -use FS::cdr qw(_cdr_min_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Simple', - 'weight' => 20, - 'header' => 1, - 'import_fields' => [ - - # Date (MM/DD/YY) - 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 - _cdr_min_parser_maker, #( [qw( billsec duration)] ), - #sub { my($cdr, $min) = @_; - # my $sec = sprintf('%.0f', $min * 60 ); - # $cdr->billsec( $sec ); - # $cdr->duration( $sec ); - # }, - - ], -); - -1; diff --git a/FS/FS/cdr/simple2.pm b/FS/FS/cdr/simple2.pm deleted file mode 100644 index 2e4fb9098..000000000 --- a/FS/FS/cdr/simple2.pm +++ /dev/null @@ -1,51 +0,0 @@ -package FS::cdr::simple2; - -use strict; -use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year ); -use Time::Local; -use FS::cdr qw(_cdr_min_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Simple (Prerated)', - 'weight' => 25, - 'header' => 1, - 'import_fields' => [ - sub {}, #TEXT_TIME (redundant w/Time) - sub {}, #Blank - 'src', #Calling. - - #Date (YY/MM/DD) - sub { my($cdr, $date) = @_; - $date =~ /^(\d\d(\d\d)?)\/(\d{1,2})\/(\d{1,2})$/ - or die "unparsable date: $date"; #maybe we shouldn't die... - #$cdr->startdate( timelocal(0, 0, 0 ,$3, $2-1, $1) ); - ($tmp_mday, $tmp_mon, $tmp_year) = ( $4, $3-1, $1 ); - }, - - #Time - sub { my($cdr, $time) = @_; - $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) - ); - }, - - 'dst', #Dest - 'userfield', #? #DestinationDesc - - #Min - _cdr_min_parser_maker, #( [qw( billsec duration)] ), - - sub {}, #Rate XXX do something w/this, informationally??? - 'upstream_price', #Total - - 'accountcode', #ServCode - 'description', #Service_Type - ], -); - - diff --git a/FS/FS/cdr/taqua.pm b/FS/FS/cdr/taqua.pm deleted file mode 100644 index 26c0bda62..000000000 --- a/FS/FS/cdr/taqua.pm +++ /dev/null @@ -1,190 +0,0 @@ -package FS::cdr::taqua; - -use strict; -use vars qw(@ISA %info $da_rewrite); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Taqua', - 'weight' => 130, - 'header' => 1, - 'import_fields' => [ #some of these are kind arbitrary... - - #0 - #RecordType - sub { - my($cdr, $field, $conf, $hashref) = @_; - $hashref->{skiprow} = 1 unless ($field == 0 && $cdr->disposition == 100); - $cdr->cdrtypenum($field); - }, - - sub { my($cdr, $field) = @_; }, #all10#RecordVersion - sub { my($cdr, $field) = @_; }, #OrigShelfNumber - sub { my($cdr, $field) = @_; }, #OrigCardNumber - sub { my($cdr, $field) = @_; }, #OrigCircuit - sub { my($cdr, $field) = @_; }, #OrigCircuitType - 'uniqueid', #SequenceNumber - 'accountcode', #SessionNumber - 'src', #CallingPartyNumber - #'dst', #CalledPartyNumber - #CalledPartyNumber - sub { - my( $cdr, $field, $conf ) = @_; - if ( $cdr->calltypenum == 6 && $cdr->cdrtypenum == 0 ) { - $cdr->dst("+$field"); - } else { - $cdr->dst($field); - } - }, - - #10 - _cdr_date_parser_maker('startdate', 'gmt' => 1), #CallArrivalTime - _cdr_date_parser_maker('enddate', 'gmt' => 1), #CallCompletionTime - - #Disposition - #sub { my($cdr, $d ) = @_; $cdr->disposition( $disposition{$d}): }, - 'disposition', - # -1 => '', - # 0 => '', - # 100 => '', - # 101 => '', - # 102 => '', - # 103 => '', - # 104 => '', - # 105 => '', - # 201 => '', - # 203 => '', - - _cdr_date_parser_maker('answerdate', 'gmt' => 1), #DispositionTime - sub { my($cdr, $field) = @_; }, #TCAP - sub { my($cdr, $field) = @_; }, #OutboundCarrierConnectTime - sub { my($cdr, $field) = @_; }, #OutboundCarrierDisconnectTime - - #TermTrunkGroup - #it appears channels are actually part of trunk groups, but this data - #is interesting and we need a source and destination place to put it - 'dstchannel', #TermTrunkGroup - - - sub { my($cdr, $field) = @_; }, #TermShelfNumber - sub { my($cdr, $field) = @_; }, #TermCardNumber - - #20 - sub { my($cdr, $field) = @_; }, #TermCircuit - sub { my($cdr, $field) = @_; }, #TermCircuitType - 'carrierid', #OutboundCarrierId - - #BillingNumber - #'charged_party', - sub { - my( $cdr, $field, $conf ) = @_; - - #could be more efficient for the no config case, if anyone ever needs that - $da_rewrite ||= $conf->config('cdr-taqua-da_rewrite'); - - if ( $da_rewrite && $field =~ /\d/ ) { - my $rewrite = $da_rewrite; - $rewrite =~ s/\s//g; - my @rewrite = split(',', $conf->config('cdr-taqua-da_rewrite') ); - if ( grep { $field eq $_ } @rewrite ) { - $cdr->charged_party( $cdr->src() ); - $cdr->calltypenum(12); - return; - } - } - if ( $cdr->is_tollfree ) { # thankfully this is already available - $cdr->charged_party($cdr->dst); # and this - } else { - $cdr->charged_party($field); - } - }, - - sub { my($cdr, $field) = @_; }, #SubscriberNumber - 'lastapp', #ServiceName - sub { my($cdr, $field) = @_; }, #some weirdness #ChargeTime - 'lastdata', #ServiceInformation - sub { my($cdr, $field) = @_; }, #FacilityInfo - sub { my($cdr, $field) = @_; }, #all 1900-01-01 0#CallTraceTime - - #30 - sub { my($cdr, $field) = @_; }, #all-1#UniqueIndicator - sub { my($cdr, $field) = @_; }, #all-1#PresentationIndicator - sub { my($cdr, $field) = @_; }, #empty#Pin - 'calltypenum', #CallType - - #nothing below is used by QIS... - - sub { my($cdr, $field) = @_; }, #Balt/empty #OrigRateCenter - sub { my($cdr, $field) = @_; }, #Balt/empty #TermRateCenter - - #OrigTrunkGroup - #it appears channels are actually part of trunk groups, but this data - #is interesting and we need a source and destination place to put it - 'channel', #OrigTrunkGroup - - 'userfield', #empty#UserDefined - sub { my($cdr, $field) = @_; }, #empty#PseudoDestinationNumber - sub { my($cdr, $field) = @_; }, #all-1#PseudoCarrierCode - - #40 - sub { my($cdr, $field) = @_; }, #empty#PseudoANI - sub { my($cdr, $field) = @_; }, #all-1#PseudoFacilityInfo - sub { my($cdr, $field) = @_; }, #OrigDialedDigits - sub { my($cdr, $field) = @_; }, #all-1#OrigOutboundCarrier - sub { my($cdr, $field) = @_; }, #IncomingCarrierID - 'dcontext', #JurisdictionInfo - sub { my($cdr, $field) = @_; }, #OrigDestDigits - sub { my($cdr, $field) = @_; }, #huh?#InsertTime - sub { my($cdr, $field) = @_; }, #key - sub { my($cdr, $field) = @_; }, #empty#AMALineNumber - - #50 - sub { my($cdr, $field) = @_; }, #empty#AMAslpID - sub { my($cdr, $field) = @_; }, #empty#AMADigitsDialedWC - sub { my($cdr, $field) = @_; }, #OpxOffHook - sub { my($cdr, $field) = @_; }, #OpxOnHook - - #acctid - primary key - #AUTO #calldate - Call timestamp (SQL timestamp) -#clid - Caller*ID with text - #XXX src - Caller*ID number / Source number - #XXX dst - Destination extension - #dcontext - Destination context - #channel - Channel used - #dstchannel - Destination channel if appropriate - #lastapp - Last application if appropriate - #lastdata - Last application data - #startdate - Start of call (UNIX-style integer timestamp) - #answerdate - Answer time of call (UNIX-style integer timestamp) - #enddate - End time of call (UNIX-style integer timestamp) - #HACK#duration - Total time in system, in seconds - #HACK#XXX billsec - Total time call is up, in seconds - #disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY -#INT amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode. - #accountcode - CDR account number to use: account - - #uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID) - #userfield - CDR user-defined field - - #X cdrtypenum - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8) - #XXX charged_party - Service number to be billed -#upstream_currency - Wholesale currency from upstream -#X upstream_price - Wholesale price from upstream -#upstream_rateplanid - Upstream rate plan ID -#rated_price - Rated (or re-rated) price -#distance - km (need units field?) -#islocal - Local - 1, Non Local = 0 -#calltypenum - Type of call - see FS::cdr_calltype -#X description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc) -#quantity - Number of items (cdr_type 7&8 only) -#carrierid - Upstream Carrier ID (see FS::cdr_carrier) -#upstream_rateid - Upstream Rate ID - - #svcnum - Link to customer service (see FS::cust_svc) - #freesidestatus - NULL, done (or something) - ], -); - -1; diff --git a/FS/FS/cdr/taqua_om.pm b/FS/FS/cdr/taqua_om.pm deleted file mode 100644 index c94ea5923..000000000 --- a/FS/FS/cdr/taqua_om.pm +++ /dev/null @@ -1,19 +0,0 @@ -package FS::cdr::taqua_om; - -use strict; -use vars qw( %info ); -use base qw( FS::cdr::taqua ); - -%info = ( - %FS::cdr::taqua::info, - 'name' => 'Taqua OM', - 'weight' => 132, - 'header' => 0, - 'sep_char' => ';', - 'row_callback' => sub { my $row = shift; - $row =~ s/^<\d+>\|[\da-f\|]+\|(\d+;)/$1/; - $row; - }, -); - -1; diff --git a/FS/FS/cdr/telos_csv.pm b/FS/FS/cdr/telos_csv.pm deleted file mode 100644 index 3faff79cd..000000000 --- a/FS/FS/cdr/telos_csv.pm +++ /dev/null @@ -1,60 +0,0 @@ -package FS::cdr::telos_csv; - -use strict; -use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year ); -use Time::Local; -use FS::cdr qw(_cdr_min_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Telos (CSV)', - 'weight' => 535, - 'header' => 1, - 'import_fields' => [ - - # Date (MM/DD/YY) - sub { my($cdr, $date) = @_; - $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/ - or die "unparsable date: $date"; - ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 ); - }, - - # Time - sub { my($cdr, $time) = @_; - $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/ - or die "unparsable time: $time"; - $cdr->enddate( - timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year) - ); - }, - '', #RAS-Client - sub { #Record-Type - my($cdr, $rectype, $conf, $param) = @_; - $param->{skiprow} = 1 if lc($rectype) ne 'stop'; - }, - skip(24), #Full-Name, Auth-Type, User-Name, NAS-IP-Address, NAS-Port, - #Service-Type, Framed-Protocol, Framed-IP-Address, - #Framed-IP-Netmask, Framed-Routing, Filter-ID, Framed-MTU, - #Framed-Compression, Login-IP-Host, Login-Service, Login-TCP-Port, - #Callback-Number, Callback-ID, Framed-Route, Framed-IPX-Network, - #Class, Session-Timeout, Idle-Timeout, Termination-Action - #I told you it was a RADIUS log - 'dst', # Called-Station-ID, always 'X' in sample data - 'src', # Calling-Station-ID - skip(8), #NAS-Identifier, Proxy-State, Acct-Status-Type, Acct-Delay-Time, - #Acct-Input-Octets, Acct-Output-Octets, Acct-Session-Id, - #Acct-Authentic - sub { - my ($cdr, $sec) = @_; - $cdr->duration($sec); - $cdr->billsec($sec); - $cdr->startdate($cdr->enddate - $sec); - }, - skip(75), #everything else - ], -); - -sub skip { map {''} (1..$_[0]) } - -1; diff --git a/FS/FS/cdr/telos_xml.pm b/FS/FS/cdr/telos_xml.pm deleted file mode 100644 index a144f0ba4..000000000 --- a/FS/FS/cdr/telos_xml.pm +++ /dev/null @@ -1,36 +0,0 @@ -package FS::cdr::telos_xml; - -use strict; -use vars qw( @ISA %info ); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Telos (XML)', - 'weight' => 530, - 'type' => 'xml', - 'xml_format' => { - 'xmlrow' => [ 'Telos_CDRS', 'CDRecord' ], - 'xmlkeys' => [ qw( - seq_num - a_party_num - b_party_num - seize - answer - disc - ) ], - }, - - 'import_fields' => [ - 'uniqueid', - 'src', - 'dst', # usually empty for some reason - _cdr_date_parser_maker('startdate'), - _cdr_date_parser_maker('answerdate'), - _cdr_date_parser_maker('enddate'), - ], - -); - -1; diff --git a/FS/FS/cdr/transnexus.pm b/FS/FS/cdr/transnexus.pm deleted file mode 100644 index 0ed7ad4ef..000000000 --- a/FS/FS/cdr/transnexus.pm +++ /dev/null @@ -1,66 +0,0 @@ -package FS::cdr::transnexus; - -use strict; -use base qw( FS::cdr ); -use vars qw( %info ); -use MIME::Base64; -use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker ); - -%info = ( - 'name' => 'Transnexus', - 'weight' => 18, - 'type' => 'csv', - 'sep_char' => "\t", - - #listref of what to do with each field from the CDR, in order - 'import_fields' => [ - - _cdr_date_parser_maker('startddate'), #O_CallStartTime - 'src', #CallingNumberReported - 'dst', #CalledNumberReported - 'channel', #SourceDeviceName / O_ReportingDeviceName - 'dstchannel', #O_ReportingDeviceName / DestinationDeviceName - sub { $_[0]->clid( decode_base64($_[1]) ); }, #CallId - 'uniqueid', #TransactionId - 'duration', #RatedDuration - 'billsec', #O_BillingDuration - 'upstream_price', #O_BillingAmountCustCurr - ], -); - -1; - -__END__ - -O_CallStartTime - Date and time stamp of the call setup as reported in the CDR from the source device. - -CallingNumberReported - Calling number from the source device reported in authorization request to the OSPrey server. - -CalledNumberReported - Called number from the source device reported in authorization request to the OSPrey server. - ----- -1.1.1 Customer CDR Archive File - -SourceDeviceName - The IP address or Domain Name of the device which is the call source. - -O_ReportingDeviceName - IP address or Domain Name of the source (Originating) device reporting the CDR to the OSPrey Server. If a proxy is used, (such as SIP proxy for signaling or FreeRADIUS for CDR reporting) this field is the IP address of the proxy device, not the actual source device. - ---- -or 1.1.2 Provider CDR Archive File - -O_ReportingDeviceName - IP address or Domain Name of the source (Originating) device reporting the CDR to the OSPrey Server. If a proxy is used, (such as SIP proxy for signaling or FreeRADIUS for CDR reporting) this field is the IP address of the proxy device, not the actual source device. - -DestinationDeviceName - The IP address or Domain Name of the destination device. - ----- - -CallId - The Call Identifier generated by the source VoIP device. - -TransactionId - The unique Transaction Identification number created by the OSPrey server for each call - -RatedDuration - The rateable duration calculated by NexOSS. - -O_BillingDuration - The duration used to calculate the billable amount for a call from the source (Originating) network. This value is derived from RatedDuration and rounded up based on the ¿First Increment¿ or ¿Next Increment¿ rules defined in the Product or Customer Rate Plan used to rate the call. - -O_BillingAmountCustCurr - Amount billable to the source (Originating) Customer. Provided in the currency of the Product or Customer Rate Plan. - diff --git a/FS/FS/cdr/troop.pm b/FS/FS/cdr/troop.pm deleted file mode 100644 index 020af2b20..000000000 --- a/FS/FS/cdr/troop.pm +++ /dev/null @@ -1,128 +0,0 @@ -package FS::cdr::troop; - -use strict; -use base qw( FS::cdr ); -use vars qw( %info $tmp_mon $tmp_mday $tmp_year ); -use Time::Local; -#use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker ); - -%info = ( - 'name' => 'Troop', - 'weight' => 220, - 'header' => 2, - 'type' => 'xls', - - 'import_fields' => [ - - # CDR FIELD / REQUIRED / Notes - - # / No / CDR sequence number - sub {}, - - # WTN / Yes - 'charged_party', - - # Account Code / Yes / Account Code (security) and we need on invoice - 'accountcode', - - # DT / Yes / "DATE" Excel - # XXX false laziness w/bell_west.pm - sub { my($cdr, $date) = @_; - - my $datetime = DateTime::Format::Excel->parse_datetime( $date ); - $tmp_mon = $datetime->mon_0; - $tmp_mday = $datetime->mday; - $tmp_year = $datetime->year; - }, - - # Time / Yes / "TIME" excel - sub { my($cdr, $time) = @_; - #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate); - - #$sec = $time * 86400; - my $sec = int( $time * 86400 + .5); - - #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) ); - $cdr->startdate( - timelocal(0, 0, 0, $tmp_mday, $tmp_mon, $tmp_year) + $sec - ); - }, - - - # Dur. / Yes / Units = seconds - 'billsec', - - # OVS Type / Maybe / add "011" to international calls - # N = DOM LD / normal - # Z = INTL LD - # O = INTL LD - # others...? - sub { my($cdr, $ovs) = @_; - my $pre = ( $ovs =~ /^\s*[OZ]\s*$/i ) ? '011' : '1'; - $cdr->dst( $pre. $cdr->dst ) unless $cdr->dst =~ /^$pre/; - }, - - # Number / YES - 'src', - - # City / No - 'channel', - - # Prov/State / No / We will use your Freeside rating and description name - sub { my($cdr, $state) = @_; - $cdr->channel( $cdr->channel. ", $state" ) - if $state; - }, - - # Number / Yes - 'dst', - - # City / No - 'dstchannel', - - # Prov/State / No / We will use your Freeside rating and description name - sub { my($cdr, $state) = @_; - $cdr->dstchannel( $cdr->dstchannel. ", $state" ) - if $state; - }, - - # OVS / Maybe - # Would help to add "011" to international calls (if you are willing) - # (using ovs above) - sub { my($cdr, $ovs) = @_; - my @ignore = ( 'BELL', 'CANADA', 'UNITED STATES', ); - $cdr->dstchannel( $cdr->dstchannel. ", $ovs" ) - if $ovs && ! grep { $ovs =~ /^\s*$_\s*$/ } @ignore; - }, - - # CC Ind. / No / Does show if Calling card but should not be required - #'N' or 'E' - sub {}, - - # Call Charge / No / Bell billing info and is not required - 'upstream_price', - - # Account # / No / Bell billing info and is not required - sub {}, - - # Net Charge / No / Bell billing info and is not required - sub {}, - - # Surcharge / No / Taxes and is not required - sub {}, - - # GST / No / Taxes and is not required - sub {}, - - # PST / No / Taxes and is not required - sub {}, - - # HST / No / Taxes and is not required - sub {}, - - ], - -); - -1; - diff --git a/FS/FS/cdr/unitel.pm b/FS/FS/cdr/unitel.pm deleted file mode 100644 index df34a57c1..000000000 --- a/FS/FS/cdr/unitel.pm +++ /dev/null @@ -1,39 +0,0 @@ -package FS::cdr::unitel; - -use strict; -use vars qw(@ISA %info); -use FS::cdr; - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Unitel/RSLCOM', - 'weight' => 500, - 'import_fields' => [ - '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', - ] -); - -1; diff --git a/FS/FS/cdr/vitelity.pm b/FS/FS/cdr/vitelity.pm deleted file mode 100644 index 97ed0c375..000000000 --- a/FS/FS/cdr/vitelity.pm +++ /dev/null @@ -1,25 +0,0 @@ -package FS::cdr::vitelity; - -use strict; -use vars qw( @ISA %info ); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'Vitelity', - 'weight' => 100, - 'header' => 1, - 'import_fields' => [ - # Cheers to Vitelity for their concise, readable CDR format. - _cdr_date_parser_maker('startdate'), - 'src', - 'dst', - 'duration', - 'clid', - 'disposition', - 'upstream_price', - ], -); - -1; diff --git a/FS/FS/cdr/wip.pm b/FS/FS/cdr/wip.pm deleted file mode 100644 index 19c45c680..000000000 --- a/FS/FS/cdr/wip.pm +++ /dev/null @@ -1,48 +0,0 @@ -package FS::cdr::wip; - -use strict; -use vars qw( @ISA %info ); -use FS::cdr qw(_cdr_date_parser_maker); - -@ISA = qw(FS::cdr); - -%info = ( - 'name' => 'WIP', - 'weight' => 100, - 'header' => 1, - 'type' => 'csv', - 'sep_char' => ':', - 'import_fields' => [ -# All of these are based on the January 2010 version of the spec, -# except that we assume that before all the fields mentioned in the -# spec, there's a counter field. - skip(4), # counter, id, APCSJursID, RecordType - sub { my($cdr, $data, $conf, $param) = @_; - $param->{skiprow} = 1 if $data == 1; - $cdr->uniqueid($data); - }, # CDRID; is 1 for line charge records - skip(1), # AccountNumber; empty - 'charged_party', # ServiceNumber - skip(1), # ServiceNumberType - 'src', # PointOrigin - 'dst', # PointTarget - 'calltypenum', # Jurisdiction: need to remap - _cdr_date_parser_maker('startdate'), #TransactionDate - skip(3), # BillClass, TypeIDUsage, ElementID - 'duration', # PrimaryUnits - skip(6), # CompletionStatus, Latitude, Longitude, - # OriginDescription, TargetDescription, RatePeriod - 'billsec', # RatedUnits; seems to always be equal to PrimaryUnits - skip(6), #SecondsUnits, ThirdUnits, FileID, OriginalExtractSequenceNumber, - #RateClass, #ProviderClass - skip(8), #ProviderID, CurrencyCode, EquipmentTypeCode, ClassOfServiceCode, - #RateUnitsType, DistanceBandID, ZoneClass, CDRStatus - 'upstream_price', # ISPBuy - skip(2), # EUBuy, CDRFromCarrier - ], - -); - -sub skip { map {''} (1..$_[0]) } - -1; diff --git a/FS/FS/cdr_batch.pm b/FS/FS/cdr_batch.pm deleted file mode 100644 index 59cfd2c7c..000000000 --- a/FS/FS/cdr_batch.pm +++ /dev/null @@ -1,128 +0,0 @@ -package FS::cdr_batch; - -use strict; -use base qw( FS::Record ); -#use FS::Record qw( qsearch qsearchs ); - -=head1 NAME - -FS::cdr_batch - Object methods for cdr_batch records - -=head1 SYNOPSIS - - use FS::cdr_batch; - - $record = new FS::cdr_batch \%hash; - $record = new FS::cdr_batch { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::cdr_batch object represents a CDR batch. FS::cdr_batch inherits from -FS::Record. The following fields are currently supported: - -=over 4 - -=item cdrbatchnum - -primary key - -=item cdrbatch - -cdrbatch - -=item _date - -_date - - -=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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'cdr_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('cdrbatchnum') - || $self->ut_textn('cdrbatch') - || $self->ut_numbern('_date') - ; - return $error if $error; - - $self->_date(time) unless $self->_date; - - $self->SUPER::check; -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, L, 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 fe456086f..000000000 --- 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 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, 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 609c93923..000000000 --- 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 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, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/cdr_termination.pm b/FS/FS/cdr_termination.pm deleted file mode 100644 index 5e3080511..000000000 --- a/FS/FS/cdr_termination.pm +++ /dev/null @@ -1,155 +0,0 @@ -package FS::cdr_termination; - -use strict; -use base qw( FS::Record ); -use FS::Record qw( qsearch qsearchs ); - -=head1 NAME - -FS::cdr_termination - Object methods for cdr_termination records - -=head1 SYNOPSIS - - use FS::cdr_termination; - - $record = new FS::cdr_termination \%hash; - $record = new FS::cdr_termination { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::cdr_termination object represents an CDR termination status. -FS::cdr_termination inherits from FS::Record. The following fields are -currently supported: - -=over 4 - -=item cdrtermnum - -primary key - -=item acctid - -acctid - -=item termpart - -termpart - -=item rated_price - -rated_price - -=item status - -status - -=item svcnum - -svc_phone record associated with this transaction, if there is one. - -=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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'cdr_termination'; } - -=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('cdrtermnum') - || $self->ut_foreign_key('acctid', 'cdr', 'acctid') - #|| $self->ut_foreign_key('termpart', 'part_termination', 'termpart') - || $self->ut_number('termpart') - || $self->ut_float('rated_price') - || $self->ut_enum('status', [ '', 'done' ] ) # , 'skipped' ] ) - ; - 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->status($status); -# $self->rated_price($rated_price); -# $self->replace(); -#} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, L, 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 e258bf878..000000000 --- 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 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, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/cgp_rule.pm b/FS/FS/cgp_rule.pm deleted file mode 100644 index e9c50901a..000000000 --- a/FS/FS/cgp_rule.pm +++ /dev/null @@ -1,363 +0,0 @@ -package FS::cgp_rule; - -use strict; -use base qw( FS::o2m_Common FS::Record ); -use FS::Record qw( qsearch qsearchs dbh ); -use FS::cust_svc; -use FS::cgp_rule_condition; -use FS::cgp_rule_action; - -=head1 NAME - -FS::cgp_rule - Object methods for cgp_rule records - -=head1 SYNOPSIS - - use FS::cgp_rule; - - $record = new FS::cgp_rule \%hash; - $record = new FS::cgp_rule { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::cgp_rule object represents a mail filtering rule. FS::cgp_rule -inherits from FS::Record. The following fields are currently supported: - -=over 4 - -=item rulenum - -primary key - -=item name - -name - -=item comment - -comment - -=item svcnum - -svcnum - -=item priority - -priority - - -=back - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Creates a new rule. To add the rule 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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'cgp_rule'; } - -=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; - - my $error = $self->SUPER::insert(@_); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - - #conditions and actions not in yet - #$error = $self->svc_export; - #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; - - 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 = $self->cgp_rule_condition; - push @del, $self->cgp_rule_action; - - foreach my $del (@del) { - my $error = $del->delete; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } - - my $error = $self->SUPER::delete(@_); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - - $error = $self->svc_export; - 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 - -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; - - my $error = $new->SUPER::replace($old, @_); - if ( $error ) { - $dbh->rollback or die $dbh->errstr if $oldAutoCommit; - return $error; - } - - #conditions and actions not in yet - #$error = $new->svc_export; - #if ( $error ) { - # $dbh->rollback if $oldAutoCommit; - # return $error; - #} - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - ''; - -} - -=item svc_export - -Calls the replace export for any communigate exports attached to this rule's -service. - -=cut - -sub svc_export { - my $self = shift; - - my $cust_svc = $self->cust_svc; - my $svc_x = $cust_svc->svc_x; - - #_singledomain too - my @exports = $cust_svc->part_svc->part_export('communigate_pro'); - my @errors = map $_->export_replace($svc_x, $svc_x), @exports; - - @errors ? join(' / ', @errors) : ''; - -} - -=item check - -Checks all fields to make sure this is a valid rule. 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('rulenum') - || $self->ut_text('name') - || $self->ut_textn('comment') - || $self->ut_foreign_key('svcnum', 'cust_svc', 'svcnum') - || $self->ut_number('priority') - ; - return $error if $error; - - $self->SUPER::check; -} - -=item clone NEW_SVCNUM - -Clones this rule into an identical rule for the specified new service. - -If there is an error, returns the error, otherwise returns false. - -=cut - -#should return the newly inserted rule instead? used in misc/clone-cgp_rule.html - -#i should probably be transactionalized so i'm all-or-nothing -sub clone { - my( $self, $svcnum ) = @_; - - my $new = $self->new( { $self->hash } ); - $new->rulenum(''); - $new->svcnum( $svcnum ); - my $error = $new->insert; - return $error if $error; - - my @dup = $self->cgp_rule_condition; - push @dup, $self->cgp_rule_action; - - foreach my $dup (@dup) { - my $new_dup = $dup->new( { $dup->hash } ); - my $pk = $new_dup->primary_key; - $new_dup->$pk(''); - $new_dup->rulenum( $new->rulenum ); - - $error = $new_dup->insert; - return $error if $error; - - } - - $error = $new->svc_export; - return $error if $error; - - ''; - -} - -=item cust_svc - -=cut - -sub cust_svc { - my $self = shift; - qsearchs('cust_svc', { 'svcnum' => $self->svcnum } ); -} - -=item cgp_rule_condition - -Returns the conditions associated with this rule, as FS::cgp_rule_condition -objects. - -=cut - -sub cgp_rule_condition { - my $self = shift; - qsearch('cgp_rule_condition', { 'rulenum' => $self->rulenum } ); -} - -=item cgp_rule_action - -Returns the actions associated with this rule, as FS::cgp_rule_action -objects. - -=cut - -sub cgp_rule_action { - my $self = shift; - qsearch('cgp_rule_action', { 'rulenum' => $self->rulenum } ); -} - -=item arrayref - -Returns an arraref representing this rule, suitable for Communigate Pro API -commands: - -The first element specifies the rule priority. - -The second element specifies the rule name. - -The third element specifies the rule conditions. - -The fourth element specifies the rule actions. - -The fifth element specifies the rule comment. - -=cut - -sub arrayref { - my $self = shift; - [ $self->priority, - $self->name, - [ map $_->arrayref, $self->cgp_rule_condition ], - [ map $_->arrayref, $self->cgp_rule_action ], - $self->comment, - ], -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/cgp_rule_action.pm b/FS/FS/cgp_rule_action.pm deleted file mode 100644 index 71605a977..000000000 --- a/FS/FS/cgp_rule_action.pm +++ /dev/null @@ -1,141 +0,0 @@ -package FS::cgp_rule_action; - -use strict; -use base qw( FS::Record ); -use FS::Record qw( qsearch qsearchs ); -use FS::cgp_rule; - -=head1 NAME - -FS::cgp_rule_action - Object methods for cgp_rule_action records - -=head1 SYNOPSIS - - use FS::cgp_rule_action; - - $record = new FS::cgp_rule_action \%hash; - $record = new FS::cgp_rule_action { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::cgp_rule_action object represents a mail filtering action. -FS::cgp_rule_action inherits from FS::Record. The following fields are -currently supported: - -=over 4 - -=item ruleactionnum - -primary key - -=item action - -action - -=item params - -params - -=item rulenum - -rulenum - -=back - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Creates a new action. To add the action 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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'cgp_rule_action'; } - -=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 action. 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('ruleactionnum') - || $self->ut_text('action') - || $self->ut_textn('params') - || $self->ut_foreign_key('rulenum', 'cgp_rule', 'rulenum') - ; - return $error if $error; - - $self->SUPER::check; -} - -=item arrayref - -=cut - -sub arrayref { - my $self = shift; - [ $self->action, $self->params ]; -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/cgp_rule_condition.pm b/FS/FS/cgp_rule_condition.pm deleted file mode 100644 index 772e1899e..000000000 --- a/FS/FS/cgp_rule_condition.pm +++ /dev/null @@ -1,148 +0,0 @@ -package FS::cgp_rule_condition; - -use strict; -use base qw( FS::Record ); -use FS::Record qw( qsearch qsearchs ); -use FS::cgp_rule; - -=head1 NAME - -FS::cgp_rule_condition - Object methods for cgp_rule_condition records - -=head1 SYNOPSIS - - use FS::cgp_rule_condition; - - $record = new FS::cgp_rule_condition \%hash; - $record = new FS::cgp_rule_condition { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::cgp_rule_condition object represents a mail filtering condition. -FS::cgp_rule_condition inherits from FS::Record. The following fields are -currently supported: - -=over 4 - -=item ruleconditionnum - -primary key - -=item conditionname - -condition - -=item op - -op - -=item params - -params - -=item rulenum - -rulenum - -=back - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Creates a new condition. To add the condition 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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'cgp_rule_condition'; } - -=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 condition. 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('ruleconditionnum') - || $self->ut_text('conditionname') - || $self->ut_textn('op') - || $self->ut_textn('params') - || $self->ut_foreign_key('rulenum', 'cgp_rule', 'rulenum') - ; - return $error if $error; - - $self->SUPER::check; -} - -=item arrayref - -Returns an array reference of the conditionname, op and params fields. - -=cut - -sub arrayref { - my $self = shift; - [ map $self->$_, qw( conditionname op params ) ]; -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/class_Common.pm b/FS/FS/class_Common.pm deleted file mode 100644 index 5ee8208f4..000000000 --- a/FS/FS/class_Common.pm +++ /dev/null @@ -1,143 +0,0 @@ -package FS::class_Common; - -use strict; -use base qw( FS::Record ); -use FS::Record qw( qsearch qsearchs ); - -=head1 NAME - -FS::class_Common - Base class for classification classes - -=head1 SYNOPSIS - -use base qw( FS::class_Common ); -use FS::category_table; #should use this - -#required -sub _target_table { 'table_name'; } - -#optional for non-standard names -sub _target_column { 'classnum'; } #default is classnum -sub _category_table { 'table_name'; } #default is to replace s/class/category/ - -=head1 DESCRIPTION - -FS::class_Common is a base class for classes which provide a classification for -other classes, such as pkg_class or cust_class. - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Creates a new classification. To add the classfication to the database, see -L<"insert">. - -=cut - -=item insert - -Adds this classification to the database. If there is an error, returns the -error, otherwise returns false. - -=item delete - -Deletes this classification from the database. Only classifications with no -associated target objects can be deleted. If there is an error, returns -the error, otherwise returns false. - -=cut - -sub delete { - my $self = shift; - - return "Can't delete a ". $self->table. - " with ". $self->_target_table. " records!" - if qsearch( $self->_target_table, - { $self->_target_column => $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 classification. 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->ut_foreign_keyn( 'categorynum', - $self->_category_table, - 'categorynum', - ) - or $self->ut_enum('disabled', [ '', 'Y' ] ) - or $self->SUPER::check; - -} - -=item category - -Returns the category record associated with this class, or false if there is -none. - -=cut - -sub category { - my $self = shift; - qsearchs($self->_category_table, { 'categorynum' => $self->categorynum } ); -} - -=item categoryname - -Returns the category name associated with this class, or false if there -is none. - -=cut - -sub categoryname { - my $category = shift->category; - $category ? $category->categoryname : ''; -} - -#required -sub _target_table { - my $self = shift; - die "_target_table unspecified for $self"; -} - -#defaults - -sub _target_column { 'classnum'; } - -use vars qw( $_category_table ); -sub _category_table { - return $_category_table if $_category_table; - my $self = shift; - $_category_table = $self->table; - $_category_table =~ s/class/category/ # s/_class$/_category/ - or die "can't determine an automatic category table for $_category_table"; - $_category_table; -} - -=head1 BUGS - -=head1 SEE ALSO - -L, L, L - -=cut - -1; diff --git a/FS/FS/clientapi_session.pm b/FS/FS/clientapi_session.pm deleted file mode 100644 index f71a126bd..000000000 --- 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 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, , 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 085e95642..000000000 --- a/FS/FS/clientapi_session_field.pm +++ /dev/null @@ -1,124 +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) - -=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 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 - -=head1 SEE ALSO - -L, L, L, 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 3faab1470..000000000 --- a/FS/FS/conf.pm +++ /dev/null @@ -1,114 +0,0 @@ -package FS::conf; - -use strict; -use vars qw( @ISA ); -use FS::Record; - -@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 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, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm deleted file mode 100644 index 774aed088..000000000 --- a/FS/FS/contact.pm +++ /dev/null @@ -1,300 +0,0 @@ -package FS::contact; - -use strict; -use base qw( FS::Record ); -use FS::Record qw( qsearch qsearchs dbh ); -use FS::prospect_main; -use FS::cust_main; -use FS::cust_location; -use FS::contact_phone; -use FS::contact_email; - -=head1 NAME - -FS::contact - Object methods for contact records - -=head1 SYNOPSIS - - use FS::contact; - - $record = new FS::contact \%hash; - $record = new FS::contact { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::contact object represents an example. FS::contact inherits from -FS::Record. The following fields are currently supported: - -=over 4 - -=item contactnum - -primary key - -=item prospectnum - -prospectnum - -=item custnum - -custnum - -=item locationnum - -locationnum - -=item last - -last - -=item first - -first - -=item title - -title - -=item comment - -comment - -=item disabled - -disabled - - -=back - -=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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'contact'; } - -=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{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 $pf ( grep { /^phonetypenum(\d+)$/ && $self->get($_) =~ /\S/ } - keys %{ $self->hashref } ) { - $pf =~ /^phonetypenum(\d+)$/ or die "wtf (daily, the)"; - my $phonetypenum = $1; - - my $contact_phone = new FS::contact_phone { - 'contactnum' => $self->contactnum, - 'phonetypenum' => $phonetypenum, - _parse_phonestring( $self->get($pf) ), - }; - $error = $contact_phone->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } - - if ( $self->get('emailaddress') =~ /\S/ ) { - my $contact_email = new FS::contact_email { - 'contactnum' => $self->contactnum, - 'emailaddress' => $self->get('emailaddress'), - }; - $error = $contact_email->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 - -# XXX delete contact_phone, contact_email - -=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{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; - } - - foreach my $pf ( grep { /^phonetypenum(\d+)$/ && $self->get($_) } - keys %{ $self->hashref } ) { - $pf =~ /^phonetypenum(\d+)$/ or die "wtf (daily, the)"; - my $phonetypenum = $1; - - my %cp = ( 'contactnum' => $self->contactnum, - 'phonetypenum' => $phonetypenum, - ); - my $contact_phone = qsearchs('contact_phone', \%cp) - || new FS::contact_phone \%cp; - - my %cpd = _parse_phonestring( $self->get($pf) ); - $contact_phone->set( $_ => $cpd{$_} ) foreach keys %cpd; - - my $method = $contact_phone->contactphonenum ? 'replace' : 'insert'; - - $error = $contact_phone->$method; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - - ''; - -} - -#i probably belong in contact_phone.pm -sub _parse_phonestring { - my $value = shift; - - my($countrycode, $extension) = ('1', ''); - - #countrycode - if ( $value =~ s/^\s*\+\s*(\d+)// ) { - $countrycode = $1; - } else { - $value =~ s/^\s*1//; - } - #extension - if ( $value =~ s/\s*(ext|x)\s*(\d+)\s*$//i ) { - $extension = $2; - } - - ( 'countrycode' => $countrycode, - 'phonenum' => $value, - 'extension' => $extension, - ); -} - -=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('contactnum') - || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum') - || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum') - || $self->ut_foreign_keyn('locationnum', 'cust_location', 'locationnum') - || $self->ut_textn('last') - || $self->ut_textn('first') - || $self->ut_textn('title') - || $self->ut_textn('comment') - || $self->ut_enum('disabled', [ '', 'Y' ]) - ; - return $error if $error; - - return "No prospect or customer!" unless $self->prospectnum || $self->custnum; - return "Prospect and customer!" if $self->prospectnum && $self->custnum; - - return "One of first name, last name, or title must have a value" - if ! grep $self->$_(), qw( first last title); - - $self->SUPER::check; -} - -sub line { - my $self = shift; - my $data = $self->first. ' '. $self->last; - $data .= ', '. $self->title - if $self->title; - $data .= ' ('. $self->comment. ')' - if $self->comment; - $data; -} - -=back - -=head1 BUGS - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/contact_email.pm b/FS/FS/contact_email.pm deleted file mode 100644 index 1276d8d68..000000000 --- a/FS/FS/contact_email.pm +++ /dev/null @@ -1,128 +0,0 @@ -package FS::contact_email; - -use strict; -use base qw( FS::Record ); -use FS::Record qw( qsearch qsearchs ); - -=head1 NAME - -FS::contact_email - Object methods for contact_email records - -=head1 SYNOPSIS - - use FS::contact_email; - - $record = new FS::contact_email \%hash; - $record = new FS::contact_email { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::contact_email object represents an example. FS::contact_email inherits from -FS::Record. The following fields are currently supported: - -=over 4 - -=item contactemailnum - -primary key - -=item contactnum - -contactnum - -=item emailaddress - -emailaddress - - -=back - -=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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'contact_email'; } - -=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('contactemailnum') - || $self->ut_number('contactnum') - || $self->ut_text('emailaddress') - ; - return $error if $error; - - $self->SUPER::check; -} - -=back - -=head1 BUGS - -The author forgot to customize this manpage. - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/contact_phone.pm b/FS/FS/contact_phone.pm deleted file mode 100644 index ad8e8f737..000000000 --- a/FS/FS/contact_phone.pm +++ /dev/null @@ -1,143 +0,0 @@ -package FS::contact_phone; - -use strict; -use base qw( FS::Record ); -use FS::Record qw( qsearch qsearchs ); - -=head1 NAME - -FS::contact_phone - Object methods for contact_phone records - -=head1 SYNOPSIS - - use FS::contact_phone; - - $record = new FS::contact_phone \%hash; - $record = new FS::contact_phone { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::contact_phone object represents an example. FS::contact_phone inherits from -FS::Record. The following fields are currently supported: - -=over 4 - -=item contactphonenum - -primary key - -=item contactnum - -contactnum - -=item phonetypenum - -phonetypenum - -=item countrycode - -countrycode - -=item phonenum - -phonenum - -=item extension - -extension - - -=back - -=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 method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'contact_phone'; } - -=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('contactphonenum') - || $self->ut_number('contactnum') - || $self->ut_number('phonetypenum') - || $self->ut_text('countrycode') - || $self->ut_text('phonenum') - || $self->ut_textn('extension') - ; - return $error if $error; - - $self->SUPER::check; -} - -=back - -=head1 BUGS - -The author forgot to customize this manpage. - -=head1 SEE ALSO - -L, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/cust_attachment.pm b/FS/FS/cust_attachment.pm deleted file mode 100644 index 5e5e07673..000000000 --- a/FS/FS/cust_attachment.pm +++ /dev/null @@ -1,199 +0,0 @@ -package FS::cust_attachment; - -use strict; -use base qw( FS::otaker_Mixin FS::Record ); -use Carp; -use FS::Record qw( qsearch qsearchs ); -use FS::Conf; - -=head1 NAME - -FS::cust_attachment - Object methods for cust_attachment records - -=head1 SYNOPSIS - - use FS::cust_attachment; - - $record = new FS::cust_attachment \%hash; - $record = new FS::cust_attachment { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::cust_attachment object represents a file attached to a L -object. FS::cust_attachment inherits from FS::Record. The following fields -are currently supported: - -=over 4 - -=item attachnum - -Primary key (assigned automatically). - -=item custnum - -Customer number (see L). - -=item _date - -The date the record was last updated. - -=item usernum - -Order taker (see L) - -=item filename - -The file's name. - -=item mime_type - -The Content-Type of the file. - -=item body - -The contents of the file. - -=item disabled - -If the attachment was disabled, this contains the date it was disabled. - -=back - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Creates a new attachment object. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'cust_attachment'; } - -sub nohistory_fields { 'body'; } - -=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 - -# 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 $conf = new FS::Conf; - my $error; - if($conf->config('disable_cust_attachment') ) { - $error = 'Attachments disabled (see configuration)'; - } - - $error = - $self->ut_numbern('attachnum') - || $self->ut_number('custnum') - || $self->ut_numbern('_date') - || $self->ut_textn('otaker') - || $self->ut_text('filename') - || $self->ut_text('mime_type') - || $self->ut_numbern('disabled') - || $self->ut_anything('body') - ; - if($conf->config('max_attachment_size') - and $self->size > $conf->config('max_attachment_size') ) { - $error = 'Attachment too large' - } - return $error if $error; - - $self->SUPER::check; -} - -=item size - -Returns the size of the attachment in bytes. - -=cut - -sub size { - my $self = shift; - return length($self->body); -} - -#false laziness w/otaker_Mixin & cust_main_note -sub otaker { - my $self = shift; - if ( scalar(@_) ) { #set - my $otaker = shift; - my($l,$f) = (split(', ', $otaker)); - my $access_user = qsearchs('access_user', { 'username'=>$otaker } ) - || qsearchs('access_user', { 'first'=>$f, 'last'=>$l } ) - or croak "can't set otaker: $otaker not found!"; #confess? - $self->usernum( $access_user->usernum ); - $otaker; #not sure return is used anywhere, but just in case - } else { #get - if ( $self->usernum ) { - $self->access_user->username; - } elsif ( length($self->get('otaker')) ) { - $self->get('otaker'); - } else { - ''; - } - } -} - -# Used by FS::Upgrade to migrate to a new database. -sub _upgrade_data { # class method - my ($class, %opts) = @_; - $class->_upgrade_otaker(%opts); -} - -=back - -=head1 BUGS - -Doesn't work on non-Postgres systems. - -=head1 SEE ALSO - -L, 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 5a5aecbf3..000000000 --- a/FS/FS/cust_bill.pm +++ /dev/null @@ -1,4710 +0,0 @@ -package FS::cust_bill; - -use strict; -use vars qw( @ISA $DEBUG $me $conf $money_char $date_format $rdate_format ); -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 Storable qw( freeze thaw ); -use FS::UID qw( datasrc ); -use FS::Misc qw( send_email send_fax generate_ps generate_pdf do_print ); -use FS::Record qw( qsearch qsearchs dbh ); -use FS::cust_main_Mixin; -use FS::cust_main; -use FS::cust_statement; -use FS::cust_bill_pkg; -use FS::cust_bill_pkg_display; -use FS::cust_bill_pkg_detail; -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; -use FS::bill_batch; -use FS::cust_bill_batch; - -@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') || '$'; - $date_format = $conf->config('date_format') || '%x'; - $rdate_format = $conf->config('date_format') || '%m/%d/%Y'; -} ); - -=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 records -(see L). FS::cust_bill inherits from FS::Record. The -following fields are currently supported: - -Regular fields - -=over 4 - -=item invnum - primary key (assigned automatically for new invoices) - -=item custnum - customer (see L) - -=item _date - specified as a UNIX timestamp; see L. Also see -L and L for conversion functions. - -=item charged - amount of this invoice - -=item invoice_terms - optional terms override for this specific invoice - -=back - -Customer info at invoice generation time - -=over 4 - -=item previous_balance - -=item billing_balance - -=back - -Deprecated - -=over 4 - -=item printed - deprecated - -=back - -Specific use cases - -=over 4 - -=item closed - books closed flag, empty or `Y' - -=item statementnum - invoice aggregation (see L) - -=item agent_invid - legacy invoice number - -=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). - -=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. - -=cut - -sub insert { - my $self = shift; - warn "$me insert 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'; - - 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 ( $self->get('cust_bill_pkg') ) { - foreach my $cust_bill_pkg ( @{$self->get('cust_bill_pkg')} ) { - $cust_bill_pkg->invnum($self->invnum); - my $error = $cust_bill_pkg->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't create invoice line item: $error"; - } - } - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - ''; - -} - -=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; - - 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 $table (qw( - cust_bill_event - cust_event - cust_credit_bill - cust_bill_pay - cust_bill_pay - cust_credit_bill - cust_pay_batch - cust_bill_pay_batch - cust_bill_pkg - )) { - - foreach my $linked ( $self->$table() ) { - my $error = $linked->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 ] - -You can, but probably shouldn't modify invoices... - -Replaces the OLD_RECORD with this one in the database, or, if OLD_RECORD is not -supplied, replaces this record. If there is an error, returns the error, -otherwise returns false. - -=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 modify closed invoice" if $old->closed =~ /^Y/i; - #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_foreign_key('custnum', 'cust_main', 'custnum' ) - || $self->ut_numbern('_date') - || $self->ut_money('charged') - || $self->ut_numbern('printed') - || $self->ut_enum('closed', [ '', 'Y' ]) - || $self->ut_foreign_keyn('statementnum', 'cust_statement', 'statementnum' ) - || $self->ut_numbern('agent_invid') #varchar? - ; - return $error if $error; - - $self->_date(time) unless $self->_date; - - $self->printed(0) if $self->printed eq ''; - - $self->SUPER::check; -} - -=item display_invnum - -Returns the displayed invoice number for this invoice: agent_invid if -cust_bill-default_agent_invid is set and it has a value, invnum otherwise. - -=cut - -sub display_invnum { - my $self = shift; - if ( $conf->exists('cust_bill-default_agent_invid') && $self->agent_invid ){ - return $self->agent_invid; - } else { - return $self->invnum; - } -} - -=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) for this invoice. - -=cut - -sub cust_bill_pkg { - my $self = shift; - qsearch( - { 'table' => 'cust_bill_pkg', - 'hashref' => { 'invnum' => $self->invnum }, - 'order_by' => 'ORDER BY billpkgnum', - } - ); -} - -=item cust_bill_pkg_pkgnum PKGNUM - -Returns the line items (see L) for this invoice and -specified pkgnum. - -=cut - -sub cust_bill_pkg_pkgnum { - my( $self, $pkgnum ) = @_; - qsearch( - { 'table' => 'cust_bill_pkg', - 'hashref' => { 'invnum' => $self->invnum, - 'pkgnum' => $pkgnum, - }, - 'order_by' => 'ORDER BY billpkgnum', - } - ); -} - -=item cust_pkg - -Returns the packages (see L) corresponding to the line items for -this invoice. - -=cut - -sub cust_pkg { - my $self = shift; - my @cust_pkg = map { $_->pkgnum > 0 ? $_->cust_pkg : () } - $self->cust_bill_pkg; - my %saw = (); - grep { ! $saw{$_->pkgnum}++ } @cust_pkg; -} - -=item no_auto - -Returns true if any of the packages (or their definitions) corresponding to the -line items for this invoice have the no_auto flag set. - -=cut - -sub no_auto { - my $self = shift; - grep { $_->no_auto || $_->part_pkg->no_auto } $self->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) 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) 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) 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) 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) 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) 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) 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 } ) - #; -} - -sub cust_pay_batch { - my $self = shift; - qsearch('cust_pay_batch', { 'invnum' => $self->invnum } ); -} - -sub cust_bill_pay_batch { - my $self = shift; - qsearch('cust_bill_pay_batch', { 'invnum' => $self->invnum } ); -} - -=item cust_bill_pay - -Returns all payment applications (see L) for this invoice. - -=cut - -sub cust_bill_pay { - my $self = shift; - map { $_ } #return $self->num_cust_bill_pay unless wantarray; - sort { $a->_date <=> $b->_date } - qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum } ); -} - -=item cust_credited - -=item cust_credit_bill - -Returns all applied credits (see L) for this invoice. - -=cut - -sub cust_credited { - my $self = shift; - map { $_ } #return $self->num_cust_credit_bill unless wantarray; - sort { $a->_date <=> $b->_date } - qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum } ) - ; -} - -sub cust_credit_bill { - shift->cust_credited(@_); -} - -=item cust_bill_pay_pkgnum PKGNUM - -Returns all payment applications (see L) for this invoice -with matching pkgnum. - -=cut - -sub cust_bill_pay_pkgnum { - my( $self, $pkgnum ) = @_; - map { $_ } #return $self->num_cust_bill_pay_pkgnum($pkgnum) unless wantarray; - sort { $a->_date <=> $b->_date } - qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum, - 'pkgnum' => $pkgnum, - } - ); -} - -=item cust_credited_pkgnum PKGNUM - -=item cust_credit_bill_pkgnum PKGNUM - -Returns all applied credits (see L) for this invoice -with matching pkgnum. - -=cut - -sub cust_credited_pkgnum { - my( $self, $pkgnum ) = @_; - map { $_ } #return $self->num_cust_credit_bill_pkgnum($pkgnum) unless wantarray; - sort { $a->_date <=> $b->_date } - qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum, - 'pkgnum' => $pkgnum, - } - ); -} - -sub cust_credit_bill_pkgnum { - shift->cust_credited_pkgnum(@_); -} - -=item tax - -Returns the tax amount (see L) 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) and credit -applications (see L). - -=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; -} - -sub owed_pkgnum { - my( $self, $pkgnum ) = @_; - - #my $balance = $self->charged; - my $balance = 0; - $balance += $_->setup + $_->recur for $self->cust_bill_pkg_pkgnum($pkgnum); - - $balance -= $_->amount for $self->cust_bill_pay_pkgnum($pkgnum); - $balance -= $_->amount for $self->cust_credited_pkgnum($pkgnum); - - $balance = sprintf( "%.2f", $balance); - $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp - $balance; -} - -=item apply_payments_and_credits [ OPTION => VALUE ... ] - -Applies unapplied payments and credits to this invoice. - -A 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. - -If there is an error, returns the error, otherwise returns false. - -=cut - -sub apply_payments_and_credits { - 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; - - $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; - - if ( $conf->exists('pkg-balances') ) { - # limit @payments & @credits to those w/ a pkgnum grepped from $self - my %pkgnums = map { $_ => 1 } map $_->pkgnum, $self->cust_bill_pkg; - @payments = grep { ! $_->pkgnum || $pkgnums{$_->pkgnum} } @payments; - @credits = grep { ! $_->pkgnum || $pkgnums{$_->pkgnum} } @credits; - } - - 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"; - } - - my $unapp_amount; - if ( $app eq 'pay' ) { - - my $payment = shift @payments; - $unapp_amount = $payment->unapplied; - $app = new FS::cust_bill_pay { 'paynum' => $payment->paynum }; - $app->pkgnum( $payment->pkgnum ) - if $conf->exists('pkg-balances') && $payment->pkgnum; - - } elsif ( $app eq 'credit' ) { - - my $credit = shift @credits; - $unapp_amount = $credit->credited; - $app = new FS::cust_credit_bill { 'crednum' => $credit->crednum }; - $app->pkgnum( $credit->pkgnum ) - if $conf->exists('pkg-balances') && $credit->pkgnum; - - } else { - die "guru meditation #12 and 35"; - } - - my $owed; - if ( $conf->exists('pkg-balances') && $app->pkgnum ) { - warn "owed_pkgnum ". $app->pkgnum; - $owed = $self->owed_pkgnum($app->pkgnum); - } else { - $owed = $self->owed; - } - next unless $owed > 0; - - warn "min ( $unapp_amount, $owed )\n" if $DEBUG; - $app->amount( sprintf('%.2f', min( $unapp_amount, $owed ) ) ); - - $app->invnum( $self->invnum ); - - my $error = $app->insert(%options); - 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 OPTION => VALUE ... - -Options: - -=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 - -=item notice_name - -notice name instead of "Invoice", optional - -=back - -Returns an argument list to be passed to L. - -=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'), - ); - - my %opt = ( - 'unsquelch_cdr' => $conf->exists('voip-cdr_email'), - 'template' => $args{'template'}, - 'notice_name' => ( $args{'notice_name'} || 'Invoice' ), - ); - - my $cust_main = $self->cust_main; - - if (ref($args{'to'}) eq 'ARRAY') { - $return{'to'} = $args{'to'}; - } else { - $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ } - $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(\%opt) ]; - } - - } - - $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 $logo; - my $agentnum = $cust_main->agentnum; - if ( defined($args{'template'}) && length($args{'template'}) - && $conf->exists( 'logo_'. $args{'template'}. '.png', $agentnum ) - ) - { - $logo = 'logo_'. $args{'template'}. '.png'; - } else { - $logo = "logo.png"; - } - my $image_data = $conf->config_binary( $logo, $agentnum); - - my $image = build MIME::Entity - 'Type' => 'image/png', - 'Encoding' => 'base64', - 'Data' => $image_data, - 'Filename' => 'logo.png', - 'Content-ID' => "<$content_id>", - ; - - $alternative->attach( - 'Type' => 'text/html', - 'Encoding' => 'quoted-printable', - 'Data' => [ '', - ' ', - ' ', - ' '. encode_entities($return{'subject'}), - ' ', - ' ', - ' ', - $self->print_html({ 'cid'=>$content_id, %opt }), - ' ', - '', - ], - 'Disposition' => 'inline', - #'Filename' => 'invoice.pdf', - ); - - my @otherparts = (); - if ( $cust_main->email_csv_cdr ) { - - push @otherparts, build MIME::Entity - 'Type' => 'text/csv', - 'Encoding' => '7bit', - 'Data' => [ map { "$_\n" } - $self->call_details('prepend_billed_number' => 1) - ], - 'Disposition' => 'attachment', - 'Filename' => 'usage-'. $self->invnum. '.csv', - ; - - } - - 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(\%opt); - - $return{'mimeparts'} = [ $related, $pdf, @otherparts ]; - - } else { - - #no other attachment: - # multipart/related - # multipart/alternative - # text/plain - # text/html - # image/png - - $return{'content-type'} = 'multipart/related'; - $return{'mimeparts'} = [ $alternative, $image, @otherparts ]; - $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(\%opt) } - ]; - } - - 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(\%opt) ]; - } - - } - - } - - %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-'. $self->invnum. '.pdf', - ); -} - -=item send HASHREF | [ TEMPLATE [ , AGENTNUM [ , INVOICE_FROM [ , AMOUNT ] ] ] ] - -Sends this invoice to the destinations configured for this customer: sends -email, prints and/or faxes. See L. - -Options can be passed as a hashref (recommended) or as a list of up to -four values for templatename, agentnum, invoice_from and amount. - -I