Advertising
- Something
- Monday, September 3rd, 2012 at 1:25:07pm MDT
- <?php
- // Copyright (c) 2012 PHP Experts, Inc.
- // All rights reserved.
- // Iteration 3: Create Tax Drivers.
- // This uses my Thrive framework, which extends
- // the Flourish framework.
- // NOTE: Using require, or require_once has performance penalties that aren't
- // necessary. If the file isn't loaded successfully, the app will crash
- // anyway.
- include_once 'thrive/Autoloader.php';
- new Thrive_Autoloader;
- // Iteration 2: Create the Data Models.
- // TIP: By always building out class datatypes instead of relying on hashmaps (array keys0, you
- // make it much easier to create what's called an API that people can easily use later.
- class TaxBracket
- {
- public $min;
- public $max;
- public $rate;
- }
- // Let's create an interface for our new taxing system.
- interface API_Tax
- {
- public function getTaxLiability(fMoney $revenue, fMoney $deductions);
- }
- // It makes more sense to use a Factory for this.
- // Uses the Factory Design Pattern.
- class USFederalTaxesFactory
- {
- {
- // TODO: Rearchitect out the magic Constants.
- // TODO: Add a proper Service Locator Pattern here...
- 'ssi' => 'SocialSecurityTax',
- 'medicare' => 'MedicareTax',
- //'unemployment' => 'UnemploymentTax',
- );
- foreach ($taxes as $taxName => $taxClass)
- {
- $tax = new $taxClass();
- /** @var $tax API_Tax */
- $liabilities[$taxName] = $tax->getTaxLiability($revenue, $deductions);
- }
- $tax = new UnemploymentTax($numOfEmployees);
- $liabilities['unemployment'] = $tax->getTaxLiability($revenue, $deductions);
- $incomeTax = new USFederalIncomeTax($revenue, $deductions, $liabilities);
- return $incomeTax;
- }
- }
- // FIXME: Figure out a way to save the API_Tax interface ;-/
- class USFederalIncomeTax /* implements API_Tax*/
- {
- // AVOID MAGIC CONSTANTS.
- // FIXME: These need to be moved to their own classes. It's a serious weakness right now!!!!
- const INCOME_TAX_KEY = 'income';
- const SSI_TAX_KEY = 'ssi';
- const MEDCARE_TAX_KEY = 'medicare';
- const UNEMPLOYMENT_TAX_KEY = 'unemployment';
- /** @var TaxBracketManager */
- protected $bracketManager;
- /** @var TaxBracket[] */
- protected $brackets;
- /** @var fMoney */
- protected $revenue;
- /** @var fMoney */
- protected $deductions;
- /** @var fMoney[] */
- protected $taxLiabilities;
- public function __construct(fMoney $revenue, fMoney $deductions, array $otherTaxLiabilities, $bracketManager = null)
- {
- $this->amountOwed = new fMoney('0', 'USD');
- $this->revenue = $revenue;
- $this->deductions = $deductions;
- $this->taxLiabilities = $otherTaxLiabilities;
- if ($bracketManager === null)
- {
- $bracketManager = new TaxBracketManager();
- }
- $this->bracketManager = $bracketManager;
- }
- /**
- * @param $tax
- * @return fMoney
- * @throws LogicException*/
- public function getLiabilityByTax($tax)
- {
- {
- throw new LogicException("No tax liabilities named '$tax'");
- }
- return $this->taxLiabilities[$tax];
- }
- public function getTaxLiability()
- {
- $brackets = $this->fetchTaxBracketRates();
- // Federal income tax algorithm works like this:
- // Total Revenue - Qualified Deductions - Other Fed Taxes -> Tax Brackets -> Rate.
- // Minimum possible taxes owed: $0.
- $otherTaxes = new fMoney(0, 'USD');
- // TODO: It'd be a nice-to-have to be able to dynamically figure out which
- // taxes are non-income and just do a foreach() here...
- $otherTaxes = $otherTaxes->add($this->getLiabilityByTax(self::SSI_TAX_KEY));
- $otherTaxes = $otherTaxes->add($this->getLiabilityByTax(self::MEDCARE_TAX_KEY));
- $otherTaxes = $otherTaxes->add($this->getLiabilityByTax(self::UNEMPLOYMENT_TAX_KEY));
- $amountOwed = $this->calculateTaxLiability($this->revenue, $this->deductions, $otherTaxes);
- $this->taxLiabilities[self::INCOME_TAX_KEY] = $amountOwed;
- return $amountOwed;
- }
- protected function calculateTaxLiability(fMoney $taxableRevenue, fMoney $deductions, fMoney $otherTaxes)
- {
- {
- echo "<pre>";
- echo "[income] Total revenue: $taxableRevenue\n";
- }
- $taxableRevenue = $taxableRevenue->sub($deductions);
- $taxableRevenue = $taxableRevenue->sub($otherTaxes);
- {
- echo "[income] Taxable revenue: $taxableRevenue\n";
- echo "----------\n";
- }
- $totalTaxLiability = new fMoney(0, 'USD');
- foreach ($this->brackets as /** @var TaxBracket */ $bracket)
- {
- // Assume 500 0000
- // Algorithm: Get subvalue -> get liability -> add liability -> subtract subvalue -> continue
- if ($bracket->max !== null)
- {
- $amountTaxedInBracket = $bracket->max - $bracket->min;
- }
- else
- {
- $amountTaxedInBracket = $taxableRevenue;
- }
- if ($taxableRevenue->lte($amountTaxedInBracket))
- {
- $amountTaxedInBracket = $taxableRevenue;
- }
- $taxLiability = new fMoney($amountTaxedInBracket, 'USD');
- $taxLiability = $taxLiability->mul($bracket->rate);
- $totalTaxLiability = $totalTaxLiability->add($taxLiability);
- $taxableRevenue = $taxableRevenue->sub($amountTaxedInBracket);
- {
- echo "[income] Bracket tax rate: {$bracket->rate}\n";
- echo "[income] Income taxed in bracket: {$bracket->min} ... {$bracket->max}\n";
- echo "[income] Amount in bracket: $amountTaxedInBracket\n";
- echo "[income] Tax Lability in bracket: $taxLiability\n";
- echo "[income] Taxable revenue: $taxableRevenue\n";
- echo "[income] Total Taxable liability: $totalTaxLiability\n";
- echo "------------\n";
- }
- if ($taxableRevenue->lte(0))
- {
- break;
- }
- }
- {
- echo "</pre>";
- }
- return $totalTaxLiability;
- }
- protected function fetchTaxBracketRates()
- {
- if ($this->brackets !== null)
- {
- return $this->brackets;
- }
- // TODO: Load this from a database table.
- // FIXME: Add more brackets.
- // TIP: By doing a little extra work now, all i have to do later is do $pdo->fetchArray() later ;-)
- // I **LOVE** our complicated corporate income tax system!! WOOO!!!!!
- $brackets = $this->bracketManager->fetchAll();
- $this->brackets = $brackets;
- return $brackets;
- }
- }
- class TaxBracketManager
- {
- public function fetchTaxBrackets()
- {
- );
- foreach ($bracketsInfo as $i)
- {
- $bracket = new TaxBracket;
- $bracket->min = $i['min'];
- $bracket->max = $i['max'];
- $bracket->rate = $i['rate'];
- $brackets[] = $bracket;
- }
- return $brackets;
- }
- }
- // Social security tax does not allow for deductions. It is off of the entire gross wage.
- class SocialSecurityTax implements API_Tax
- {
- public function getTaxLiability(fMoney $revenue, fMoney $deductions)
- {
- $taxLiability = $this->calculateTaxLiability($revenue);
- return $taxLiability;
- }
- protected function calculateTaxLiability(fMoney $taxableRevenue)
- {
- $maxTaxable = $this->fetchMaxTaxableAmount();
- $taxRate = $this->fetchTaxRate();
- $taxLiability = $taxableRevenue->mul($taxRate);
- {
- echo "<pre>";
- echo "[ssi] Taxable Revenue: $taxableRevenue\n";
- echo "[ssi] Tax Rate: $taxRate\n";
- echo "[ssi] Total Tax Liability: $taxLiability\n";
- echo "</pre>";
- }
- return $taxLiability;
- }
- protected function fetchMaxTaxableAmount()
- {
- return new fMoney(110100, 'USD');
- }
- protected function fetchTaxRate()
- {
- return 0.062;
- }
- }
- // Medicare tax does not allow for deductions. It is off of the entire gross wage.
- class MedicareTax implements API_Tax
- {
- public function getTaxLiability(fMoney $revenue, fMoney $deductions)
- {
- $taxLiability = $this->calculateTaxLiability($revenue);
- return $taxLiability;
- }
- protected function calculateTaxLiability(fMoney $taxableRevenue)
- {
- $taxRate = $this->fetchTaxRate();
- $taxLiability = $taxableRevenue->mul($taxRate);
- {
- echo "<pre>";
- echo "[medicare] Taxable Revenue: $taxableRevenue\n";
- echo "[medicare] Tax Rate: $taxRate\n";
- echo "[medicare] Total Tax Liability: $taxLiability\n";
- echo "</pre>";
- }
- return $taxLiability;
- }
- protected function fetchTaxRate()
- {
- return 0.0145;
- }
- }
- // Unemployment Insurance tax does not allow for deductions. It is off of the entire gross wage.
- // Source: http://workforcesecurity.doleta.gov/unemploy/uitaxtopic.asp
- class UnemploymentTax implements API_Tax
- {
- protected $numOfEmployees;
- public function __construct($numOfEmployees = null)
- {
- if ($numOfEmployees !== null)
- {
- echo "Num of Employees 2.5: $numOfEmployees";
- $this->setNumberOfEmployees($numOfEmployees);
- }
- }
- public function setNumberOfEmployees($numOfEmployees)
- {
- $this->numOfEmployees = $numOfEmployees;
- }
- public function getTaxLiability(fMoney $revenue, fMoney $deductions)
- {
- {
- throw new LogicException("Cannot calculate Unemployment tax without specifying the number of employees.");
- }
- $taxLiability = new fMoney(0, 'USD');
- for ($a = 0; $a < $this->numOfEmployees; ++$a)
- {
- $taxLiability = $taxLiability->add($this->calculateTaxLiability($revenue));
- }
- return $taxLiability;
- }
- protected function calculateTaxLiability(fMoney $taxableRevenue)
- {
- if ($taxableRevenue->lte($this->fetchMinimumAmountToTax()))
- {
- return new fMoney(0, 'USD');
- }
- $maxTaxLiability = $this->fetchMaxTaxLiability();
- $taxRate = $this->fetchTaxRate();
- $taxLiability = $taxableRevenue->mul($taxRate);
- if ($taxLiability->gte($maxTaxLiability))
- {
- $taxLiability = $maxTaxLiability;
- }
- {
- echo "<pre>";
- echo "[unemployment] Taxable Revenue: $taxableRevenue\n";
- echo "[unemployment] Tax Rate: $taxRate\n";
- echo "[unemployment] Total Tax Liability: $taxLiability\n";
- echo "</pre>";
- }
- return $taxLiability;
- }
- protected function fetchMinimumAmountToTax()
- {
- return new fMoney(1500, 'USD');
- }
- protected function fetchMaxTaxLiability()
- {
- return new fMoney(56.00, 'USD');
- }
- protected function fetchTaxRate()
- {
- return 0.062;
- }
- }
- class FedTaxes
- {
- /** @var fMoney */
- public $income;
- /** @var fMoney */
- public $ssi;
- /** @var fMoney */
- public $medicare;
- /** @var fMoney */
- public $unemployment;
- /** @var fMoney */
- public $total;
- public function __construct()
- {
- $this->income = new fMoney(0, 'USD');
- $this->ssi = new fMoney(0, 'USD');
- $this->medicare = new fMoney(0, 'USD');
- $this->unemployment = new fMoney(0, 'USD');
- $this->total = new fMoney(0, 'USD');
- }
- }
- // Main execution path.
- function getTaxLiability()
- {
- {
- // I prefer filter_var($_POST) over filter_input(INPUT_POST) because it
- // is **impossible** to simulate form data in unit tests with filter_input().
- if (($grossIncome = filter_var($_POST['gross_income'], FILTER_SANITIZE_NUMBER_FLOAT)) === false)
- {
- throw new InvalidArgumentException("Invalid input for gross income, only numbers are accepted.");
- }
- if (($expenses = filter_var($_POST['expenses'], FILTER_SANITIZE_NUMBER_FLOAT)) === false)
- {
- throw new InvalidArgumentException("Invalid input for expenses, only numbers are accepted.");
- }
- if (($numOfEmployees = filter_var($_POST['num_of_employees'], FILTER_SANITIZE_NUMBER_INT)) === false)
- {
- throw new InvalidArgumentException("Invalid input for number of employees, only integers are accepted.");
- }
- echo "Num of Employees: $numOfEmployees";
- fMoney::setDefaultCurrency('USD');
- $taxManager = USFederalTaxesFactory::create(new fMoney($grossIncome), new fMoney($expenses), $numOfEmployees);
- $amountOwed = $taxManager->getTaxLiability();
- $fedTaxes = new FedTaxes;
- $fedTaxes->income = $amountOwed;
- $fedTaxes->ssi = $taxManager->getLiabilityByTax('ssi');
- $fedTaxes->medicare = $taxManager->getLiabilityByTax('medicare');
- $fedTaxes->unemployment = $taxManager->getLiabilityByTax('unemployment');
- // TODO: fMoney **really** should be able to add multiple values at once. I mean, come on!
- {
- $fedTaxes->total = $fedTaxes->total->add($fedTaxes->$tax);
- }
- return $fedTaxes;
- }
- return null;
- }
- $fedTaxes = null;
- try
- {
- $fedTaxes = getTaxLiability();
- }
- catch(InvalidArgumentException $e)
- {
- $errorMessage = "Oops! An error has occurred:<br/>\n";
- $errorMessage .= $e->getMessage();
- }
- catch(Exception $e)
- {
- $errorMessage = "Oops: An error has occured.";
- $errorMessage .= $e->getMessage();
- }
- ?>
- <?php
- // Start of the view.
- $e_numOfEmployees = isset($_POST['num_of_employees']) ? htmlspecialchars($_POST['num_of_employees']) : '';
- {
- }
- // Iteration 1: Create the HTML.
- ?>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>Federal Corporate Income Tax Calculator</title>
- <style type="text/css">
- table#tax_data_table th { text-align: left; }
- table#tax_data_table td { text-align: right; }
- </style>
- </head>
- <body>
- <h1>Federal Corporate Income Tax Calculator</h1>
- <p>This app is designed to calculate the estimated income tax of a corporation.</p>
- <h4>Disclaimer</h4>
- <p>This app is for educational purposes only. It is by no means a substitute for proper tax accounting services!</p>
- <p><em>Note: Because of <a href="http://workforcesecurity.doleta.gov/unemploy/uitaxtopic.asp">Congressional bungling</a>, the Unemployment Taxrate
- for 2011 is overstated (usually by just 0.2%).</em></p>
- <?php
- ?>
- <div id="error_messag">
- </div>
- <?php endif; ?>
- <div id="income_form">
- <form method="post">
- <table id="income_data_table">
- <tr>
- <th><label for="gross_income">Gross income:</label></th>
- <td><input type="text" name="gross_income" value="<?php echo $e_grossIncome; ?>"/></td>
- </tr>
- <tr>
- <th><label for="expenses">Expenses:</label></th>
- <td><input type="text" name="expenses" value="<?php echo $e_expenses; ?>"/></td>
- </tr>
- <tr>
- <th><label for="expenses">No. of Employees:</label></th>
- <td><input type="text" name="num_of_employees" value="<?php echo $e_numOfEmployees; ?>"/></td>
- </tr>
- <tr>
- <td span="2"><input type="submit" value="Calculate"></td>
- </tr>
- </table>
- </form>
- </div>
- <?php
- if ($fedTaxes instanceof FedTaxes):
- ?>
- <div id="taxes_data">
- <h3>Tax Information</h3>
- <table id="tax_data_table">
- <tr>
- <th>Income Tax: </th>
- </tr>
- <tr>
- <th>Social Security Tax:</th>
- </tr>
- <tr>
- <th>Medicare Tax:</th>
- </tr>
- <tr>
- <th><a href="http://workforcesecurity.doleta.gov/unemploy/uitaxtopic.asp">Unemployment Tax:</a></th>
- </tr>
- <tr>
- <th>Total Liability:</th>
- </tr>
- </table>
- </div>
- <?php endif; ?>
- </body>
- </html>
advertising
Update the Post
Either update this post and resubmit it with changes, or make a new post.
You may also comment on this post.
Please note that information posted here will expire by default in one month. If you do not want it to expire, please set the expiry time above. If it is set to expire, web search engines will not be allowed to index it prior to it expiring. Items that are not marked to expire will be indexable by search engines. Be careful with your passwords. All illegal activities will be reported and any information will be handed over to the authorities, so be good.