<commit_before><?
/* Altinn report class
 * Includes all the linked salaries and employee info
 * needed for the report.
 */

class altinn_report {
  public $salaries               = array();
  public $salary_ids             = array();
  public $salary_lines           = array();
  public $employees              = array();
  public $employee_ids           = array();
  public $only_register_employee = false;
  public $period                 = '';
  public $melding                = null; // structured object that contains the report
  public $meldingsId             = null; // mesasge id
  public $erstatterMeldingsId    = null; // replacement message id
  public $errors                 = null; // to be populated if errors occur

/* Constructor accepts the accounting period(year-month)
 * salary, employee ids, and flag if we are sending for only
 * one employee.
 * It automatically loads all the salaries and employees.
 */
  function __construct($period, $salary_ids = null, $employee_ids = null, $only_register_employee = false) {
    // if no period selected, exit
    if (empty($period)) return;
    else $this->period = $period;

    // TODO: Use and update initialize method and remove this below
    $this->only_register_employee = $only_register_employee;
    $this->salary_ids = $salary_ids;
    $this->employee_ids = $employee_ids;
    // fetch the salaries
    self::fetchSalaries($this->salary_ids);
    // fetch the employees
    self::fetchEmployees($this->employee_ids);
  }

/* Helper function to initialize all the needed parameters
 * both salary_ids and employee_ids
 */
  function initialize($salary_ids, $employee_ids) {
    self::setSalaryIDs($salary_ids);
    self::setEmployeeIDs($employee_ids);
  }

/* Helper function to set which salaries to be included
 */
  function setSalaryIDs($salary_ids) {
    $this->salary_ids = $salary_ids;
  }

/* Helper function to set which employees to be included
 */
  function setEmployeeIDs($employee_ids) {
    $this->employee_ids = $employee_ids;
  }

/* Helper function to add replacement message id
 */
  function addReplacementMessageID($message_id) {
    $this->erstatterMeldingsId = $message_id;
  }

/* Helper function to check if the variable is empty
 * calls a sub function for for string, date, amount/number,
 * org_number, name check based on the type
 */
  function checkIfEmpty($field, $error_message, $type = 'string') {
    if ($type == 'string') $is_empty = empty($field);
    elseif ($type == 'date') $is_empty = strstr($field, '0000-00-00');
    elseif ($type == 'number') $is_empty = empty($field) || ($field == 0);
    elseif ($type == 'percent') $is_empty = is_null($field);
    elseif ($type == 'org_number') $is_empty = !preg_match('/^([0-9]{9})$/', $field);
    elseif ($type == 'name') $is_empty = !preg_match(utf8_encode('/^([A-Za-zæøåÆØÅ\s]+)$/'), utf8_encode($field));
    else {
      $error_message = 'Unknown type ' . $type;
      $is_empty = true;
    }
    if ($is_empty) $this->errors[] = $error_message;
    return $is_empty;
  }

/* Helper function that returns the full name for the employee.
 */
  function fullName($employee) {
    return $employee->FirstName . ' ' . $employee->LastName;
  }

/* Helper function that returns the full name and account plan id
 * for the employee. Used for error messages.
 */
  function fullNameForErrorMessage($employee) {
    return self::fullName($employee) . '(' . $employee->AccountPlanID . ')';
  }

/* Helper function that populates the report(melding) array
 */
  function populateReportArray() {
    global $_lib;

    $org_number = $_lib['sess']->get_companydef('OrgNumber');
    $org_number = preg_replace('/\s+/', '', $org_number);
    // Error is: Organisation number missing(not set);
    self::checkIfEmpty($org_number, 'Firmaopplysning: Organisasjonsnr mangler (ikke satt)');
    // Error is: OrgNumber needs to be 9 digits long!
    self::checkIfEmpty($org_number, 'Firmaopplysning: Organisasjonsnr m&aring; v&aelig;re 9 tall langt', 'org_number');
    $leveranse = array();
    // leveranse = deliver
    // delivery date
    $leveranse['leveringstidspunkt'] = strftime('%FT%TZ', time());
    // calendar month
    // Error is: Period for report not chosen
    self::checkIfEmpty($this->period, 'Periode er ikke satt');
    $leveranse['kalendermaaned'] = $this->period;
    // salary system
    $leveranse['kildesystem'] = 'LODO';

    // government tax
    // arbeidsgiveravgift = tax that the company pays
    $sumForskuddstrekk = 0.0;
    $sumArbeidsgiveravgift = 0.0;

    // message id
    // replacement if we are sending a replacement report
    if ($this->erstatterMeldingsId) $leveranse['erstatterMeldingsId'] = $this->erstatterMeldingsId;
    // save for future use in creation of SOAP request
    $meldings_id = 'report_for_' . $org_number . '_at_' . time();
    $leveranse['meldingsId'] = $meldings_id;
    $this->meldingsId = $meldings_id;

    // opplysningspliktig = reportee
    // norskIdentifikator = norwegian identifier, personal id or company org number
    $leveranse['opplysningspliktig'] = array();
    $leveranse['opplysningspliktig']['norskIdentifikator'] = $org_number;

    // beregningskodeForArbeidsgiveravgift = calculation code for arbeidsgiveravgift
    $code_for_tax_calculation = $_lib['sess']->get_companydef('CalculationCodeForTax');
    // Error is: Code for tax calculation not set on company
    self::checkIfEmpty($code_for_tax_calculation, 'Firmaopplysning: Mangler beregningskode for arbeidsgiveravgift for firma');

    $virksomhet_array = array();
    // select salary and the employee connected to it
    // Error is: No employees for this period
    self::checkIfEmpty($this->employees, 'Det er ingen ansatte i perioden');
    // Error is: No salaries for this period
    if (!$this->only_register_employee) self::checkIfEmpty($this->salaries, 'Det er ingen l&oslash;nnslipper  i perioden');
    foreach($this->employees as $key_subcompany => $employees) {
      // generate subcompany array with all the salaries for that company
      // sumForskuddstrekk and sumArbeidsgiveravgift are sent by reference and are affected outsude the function as well
      $virksomhet = self::generateVirksomhetArray($key_subcompany, $this->salaries[$key_subcompany], $code_for_tax_calculation, $sumForskuddstrekk, $sumArbeidsgiveravgift);

      if ($virksomhet) $virksomhet_array[]['virksomhet'] = $virksomhet;
    }
    $leveranse['oppgave'] = array();
    $leveranse['oppgave']['betalingsinformasjon'] = array();
    $leveranse['oppgave']['betalingsinformasjon']['sumForskuddstrekk'] = (int) round($sumForskuddstrekk);
    $leveranse['oppgave']['betalingsinformasjon']['sumArbeidsgiveravgift'] = (int) round($sumArbeidsgiveravgift);
    foreach($virksomhet_array as $one_virksomhet) {
      $leveranse['oppgave'][] = $one_virksomhet;
    }
    $melding['Leveranse'] = $leveranse;
    $this->melding = $melding;
  }

/* Helper function to generate a subcompany array
 * Affects $sumForskuddstrekk and $sumArbeidsgiveravgift vars.
 */
  function generateVirksomhetArray($key_subcompany, $salaries, $code_for_tax_calculation, &$sumForskuddstrekk, &$sumArbeidsgiveravgift) {
    // used for arbeidsgiveravgift node
    $loennOgGodtgjoerelse = array();
    $virksomhet = array();
    $use_loennOgGodtgjoerelse = false;
    foreach($this->employees[$key_subcompany] as $key_employee => $employee) {
      // if there is no salaries for current subcompany and current employee, just generate work relation
      // because we do not want to try to loop over a null value
      if (empty($salaries[$employee->AccountPlanID])) {
        $inntektsmottaker = self::generateInntektsmottakerArray($key_subcompany, $salary, $employee, $code_for_tax_calculation, $virksomhet, $loennOgGodtgjoerelse, $sumForskuddstrekk, true);
        // income reciever
        $virksomhet[] = $inntektsmottaker;
      } else {
        foreach($salaries[$employee->AccountPlanID] as $key_salary => $salary) {
          // generate income reciever array
          // virksonhet, loennOgGodtgjoerelse and sumForskuddstrekk are affected in this function because they are sent by reference
          $inntektsmottaker = self::generateInntektsmottakerArray($key_subcompany, $salary, $employee, $code_for_tax_calculation, $virksomhet, $loennOgGodtgjoerelse, $sumForskuddstrekk);
          $use_loennOgGodtgjoerelse = true;

          $virksomhet[] = $inntektsmottaker;
        }
      }
    }
    // loennOgGodtgjoerelse = salary and refunds
    if ($use_loennOgGodtgjoerelse) {
      $virksomhet['arbeidsgiveravgift'] = $loennOgGodtgjoerelse;
    }
    foreach($loennOgGodtgjoerelse as $zone_tax_array) {
      $zone_tax = $zone_tax_array['loennOgGodtgjoerelse'];
      $sumArbeidsgiveravgift += $zone_tax['avgiftsgrunnlagBeloep'] * $zone_tax['prosentsatsForAvgiftsberegning']/100.0;
    }
    return $virksomhet;
  }

/* Helper function to generate a subcompany array
 * Affects $virksomhet, $loennOgGodtgjoerelse and $sumForskuddstrekk vars.
 * use_only_employee_info is set if we only want the work relation and not the incomes<commit_msg>Validate so we can only select one salary for each employee in one virksomhet<commit_after><?
/* Altinn report class
 * Includes all the linked salaries and employee info
 * needed for the report.
 */

class altinn_report {
  public $salaries               = array();
  public $salary_ids             = array();
  public $salary_lines           = array();
  public $employees              = array();
  public $employee_ids           = array();
  public $only_register_employee = false;
  public $period                 = '';
  public $melding                = null; // structured object that contains the report
  public $meldingsId             = null; // mesasge id
  public $erstatterMeldingsId    = null; // replacement message id
  public $errors                 = null; // to be populated if errors occur

/* Constructor accepts the accounting period(year-month)
 * salary, employee ids, and flag if we are sending for only
 * one employee.
 * It automatically loads all the salaries and employees.
 */
  function __construct($period, $salary_ids = null, $employee_ids = null, $only_register_employee = false) {
    // if no period selected, exit
    if (empty($period)) return;
    else $this->period = $period;

    // TODO: Use and update initialize method and remove this below
    $this->only_register_employee = $only_register_employee;
    $this->salary_ids = $salary_ids;
    $this->employee_ids = $employee_ids;
    // fetch the salaries
    self::fetchSalaries($this->salary_ids);
    // fetch the employees
    self::fetchEmployees($this->employee_ids);
  }

/* Helper function to initialize all the needed parameters
 * both salary_ids and employee_ids
 */
  function initialize($salary_ids, $employee_ids) {
    self::setSalaryIDs($salary_ids);
    self::setEmployeeIDs($employee_ids);
  }

/* Helper function to set which salaries to be included
 */
  function setSalaryIDs($salary_ids) {
    $this->salary_ids = $salary_ids;
  }

/* Helper function to set which employees to be included
 */
  function setEmployeeIDs($employee_ids) {
    $this->employee_ids = $employee_ids;
  }

/* Helper function to add replacement message id
 */
  function addReplacementMessageID($message_id) {
    $this->erstatterMeldingsId = $message_id;
  }

/* Helper function to check if the variable is empty
 * calls a sub function for for string, date, amount/number,
 * org_number, name check based on the type
 */
  function checkIfEmpty($field, $error_message, $type = 'string') {
    if ($type == 'string') $is_empty = empty($field);
    elseif ($type == 'date') $is_empty = strstr($field, '0000-00-00');
    elseif ($type == 'number') $is_empty = empty($field) || ($field == 0);
    elseif ($type == 'percent') $is_empty = is_null($field);
    elseif ($type == 'org_number') $is_empty = !preg_match('/^([0-9]{9})$/', $field);
    elseif ($type == 'name') $is_empty = !preg_match(utf8_encode('/^([A-Za-zæøåÆØÅ\s]+)$/'), utf8_encode($field));
    elseif ($type == 'boolean') $is_empty = $field;
    else {
      $error_message = 'Unknown type ' . $type;
      $is_empty = true;
    }
    if ($is_empty) $this->errors[] = $error_message;
    return $is_empty;
  }

/* Helper function that returns the full name for the employee.
 */
  function fullName($employee) {
    return $employee->FirstName . ' ' . $employee->LastName;
  }

/* Helper function that returns the full name and account plan id
 * for the employee. Used for error messages.
 */
  function fullNameForErrorMessage($employee) {
    return self::fullName($employee) . '(' . $employee->AccountPlanID . ')';
  }

/* Helper function that populates the report(melding) array
 */
  function populateReportArray() {
    global $_lib;

    $org_number = $_lib['sess']->get_companydef('OrgNumber');
    $org_number = preg_replace('/\s+/', '', $org_number);
    // Error is: Organisation number missing(not set);
    self::checkIfEmpty($org_number, 'Firmaopplysning: Organisasjonsnr mangler (ikke satt)');
    // Error is: OrgNumber needs to be 9 digits long!
    self::checkIfEmpty($org_number, 'Firmaopplysning: Organisasjonsnr m&aring; v&aelig;re 9 tall langt', 'org_number');
    $leveranse = array();
    // leveranse = deliver
    // delivery date
    $leveranse['leveringstidspunkt'] = strftime('%FT%TZ', time());
    // calendar month
    // Error is: Period for report not chosen
    self::checkIfEmpty($this->period, 'Periode er ikke satt');
    $leveranse['kalendermaaned'] = $this->period;
    // salary system
    $leveranse['kildesystem'] = 'LODO';

    // government tax
    // arbeidsgiveravgift = tax that the company pays
    $sumForskuddstrekk = 0.0;
    $sumArbeidsgiveravgift = 0.0;

    // message id
    // replacement if we are sending a replacement report
    if ($this->erstatterMeldingsId) $leveranse['erstatterMeldingsId'] = $this->erstatterMeldingsId;
    // save for future use in creation of SOAP request
    $meldings_id = 'report_for_' . $org_number . '_at_' . time();
    $leveranse['meldingsId'] = $meldings_id;
    $this->meldingsId = $meldings_id;

    // opplysningspliktig = reportee
    // norskIdentifikator = norwegian identifier, personal id or company org number
    $leveranse['opplysningspliktig'] = array();
    $leveranse['opplysningspliktig']['norskIdentifikator'] = $org_number;

    // beregningskodeForArbeidsgiveravgift = calculation code for arbeidsgiveravgift
    $code_for_tax_calculation = $_lib['sess']->get_companydef('CalculationCodeForTax');
    // Error is: Code for tax calculation not set on company
    self::checkIfEmpty($code_for_tax_calculation, 'Firmaopplysning: Mangler beregningskode for arbeidsgiveravgift for firma');

    $virksomhet_array = array();
    // select salary and the employee connected to it
    // Error is: No employees for this period
    self::checkIfEmpty($this->employees, 'Det er ingen ansatte i perioden');
    // Error is: No salaries for this period
    if (!$this->only_register_employee) self::checkIfEmpty($this->salaries, 'Det er ingen l&oslash;nnslipper  i perioden');
    foreach($this->employees as $key_subcompany => $employees) {
      // generate subcompany array with all the salaries for that company
      // sumForskuddstrekk and sumArbeidsgiveravgift are sent by reference and are affected outsude the function as well
      $virksomhet = self::generateVirksomhetArray($key_subcompany, $this->salaries[$key_subcompany], $code_for_tax_calculation, $sumForskuddstrekk, $sumArbeidsgiveravgift);

      if ($virksomhet) $virksomhet_array[]['virksomhet'] = $virksomhet;
    }
    $leveranse['oppgave'] = array();
    $leveranse['oppgave']['betalingsinformasjon'] = array();
    $leveranse['oppgave']['betalingsinformasjon']['sumForskuddstrekk'] = (int) round($sumForskuddstrekk);
    $leveranse['oppgave']['betalingsinformasjon']['sumArbeidsgiveravgift'] = (int) round($sumArbeidsgiveravgift);
    foreach($virksomhet_array as $one_virksomhet) {
      $leveranse['oppgave'][] = $one_virksomhet;
    }
    $melding['Leveranse'] = $leveranse;
    $this->melding = $melding;
  }

/* Helper function to generate a subcompany array
 * Affects $sumForskuddstrekk and $sumArbeidsgiveravgift vars.
 */
  function generateVirksomhetArray($key_subcompany, $salaries, $code_for_tax_calculation, &$sumForskuddstrekk, &$sumArbeidsgiveravgift) {
    // used for arbeidsgiveravgift node
    $loennOgGodtgjoerelse = array();
    $virksomhet = array();
    $use_loennOgGodtgjoerelse = false;
    foreach($this->employees[$key_subcompany] as $key_employee => $employee) {
      // if there is no salaries for current subcompany and current employee, just generate work relation
      // because we do not want to try to loop over a null value
      if (empty($salaries[$employee->AccountPlanID])) {
        $inntektsmottaker = self::generateInntektsmottakerArray($key_subcompany, $salary, $employee, $code_for_tax_calculation, $virksomhet, $loennOgGodtgjoerelse, $sumForskuddstrekk, true);
        // income receiver
        $virksomhet[] = $inntektsmottaker;
      } else {
        if (count($salaries[$employee->AccountPlanID]) > 1) {
          // Error is: There is more then 1 salary<L 1, L 3> for <name> in this report
          $l_names = array_map(function($salary) {
            return "L ". $salary->JournalID;
          }, $salaries[$employee->AccountPlanID]);

          $msg = 'Det er mere enn 1 l&oslash;nnslipp('.implode($l_names, ', ').') for '.$this->fullNameForErrorMessage($employee).' i denne rapporten';
          self::checkIfEmpty(true, $msg, 'boolean');
        }

        foreach($salaries[$employee->AccountPlanID] as $key_salary => $salary) {
          // generate income receiver array
          // virksomhet, loennOgGodtgjoerelse and sumForskuddstrekk are affected in this function because they are sent by reference
          $inntektsmottaker = self::generateInntektsmottakerArray($key_subcompany, $salary, $employee, $code_for_tax_calculation, $virksomhet, $loennOgGodtgjoerelse, $sumForskuddstrekk);
          $use_loennOgGodtgjoerelse = true;

          $virksomhet[] = $inntektsmottaker;
        }
      }
    }
    // loennOgGodtgjoerelse = salary and refunds
    if ($use_loennOgGodtgjoerelse) {
      $virksomhet['arbeidsgiveravgift'] = $loennOgGodtgjoerelse;
    }
    foreach($loennOgGodtgjoerelse as $zone_tax_array) {
      $zone_tax = $zone_tax_array['loennOgGodtgjoerelse'];
      $sumArbeidsgiveravgift += $zone_tax['avgiftsgrunnlagBeloep'] * $zone_tax['prosentsatsForAvgiftsberegning']/100.0;
    }
    return $virksomhet;
  }

/* Helper function to generate a subcompany array
 * Affects $virksomhet, $loennOgGodtgjoerelse and $sumForskuddstrekk vars.
 * use_only_employee_info is set if we only want the work relation and not the incomes
{'fraction_non_alphanumeric': 0.12145018313073942, 'fraction_numerical': 0.0028133128085354848, 'mean_word_length': 3.834488067744419, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 74, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2267894', 'n_tokens_mistral': 6236, 'n_tokens_neox': 6065, 'n_words': 1495}