needlogin = true; $me = new User(); $me->loadMe(); $this->me = $me; $this->layout()->set("me", $me); if (!$me->is(["Admin"])) { $this->redirect("Dashboard"); } } protected function indexAction() { if(!$this->me->can("Billing")) { $this->redirect("Dashboard"); } if($this->request->export_type == "bmd-invoice" || $this->request->export_type == "bmd-credit") { return $this->exportBmdAction(); } $this->layout()->setTemplate("Invoice/Index"); if ($this->request->resetFilter) { unset($_SESSION[MFAPPNAME . '-Invoice-filter']); } $filter = []; if (is_array($this->request->filter)) { $filter = $this->request->filter; $_SESSION[MFAPPNAME . '-Invoice-filter'] = $filter; } else { if (array_key_exists(MFAPPNAME . '-Invoice-filter', $_SESSION) && count($_SESSION[MFAPPNAME . '-Invoice-filter'])) { $filter = $_SESSION[MFAPPNAME . '-Invoice-filter']; } } $this->layout->set("filter", $filter); $filter = $this->getPreparedFilter($filter); // pagination defaults $pagination = []; $pagination['start'] = 0; $pagination['count'] = 50; $pagination['maxItems'] = 0; if (is_numeric($this->request->s)) { $pagination['start'] = intval($this->request->s); } //var_dump($filter);exit; $pagination['maxItems'] = InvoiceModel::count($filter); $billings = InvoiceModel::search($filter, $pagination); $this->layout()->set("invoices", $billings); $this->layout()->set("pagination", $pagination); // get Jobs $now = new DateTime("now"); $pdf_jobs = InvoiceJobModel::search(["task" => "make-invoice-pdf", "to_date>=" => $now->format("Y-m-d")]); $email_jobs = InvoiceJobModel::search(["task" => "send-invoice-email", "to_date>=" => $now->format("Y-m-d")]); $this->layout()->set("pdf_jobs", $pdf_jobs); $this->layout()->set("email_jobs", $email_jobs); // sum listing $sum_price = InvoiceModel::getSumPrice($filter); $sum_price_gross = InvoiceModel::getSumGrossPrice($filter); $sum_price_sepa = InvoiceModel::getSumPrice(array_merge($filter, ["billing_type" => "sepa"])); $sum_price_sepa_gross = InvoiceModel::getSumGrossPrice(array_merge($filter, ["billing_type" => "sepa"])); $sum_credit_price = InvoiceModel::getSumCreditPrice($filter); $sum_credit_price_gross = InvoiceModel::getSumCreditGrossPrice($filter); $sum_voicecalls_price = InvoiceModel::getSumVoicecallsPrice($filter); $sum_voicecalls_price_gross = InvoiceModel::getSumVoicecallsGrossPrice($filter); $sum_voicecalls_price_sepa = InvoiceModel::getSumVoicecallsPrice(array_merge($filter, ["billing_type" => "sepa"])); $sum_voicecalls_price_sepa_gross = InvoiceModel::getSumVoicecallsGrossPrice(array_merge($filter, ["billing_type" => "sepa"])); $this->layout()->set("sum_price", $sum_price); $this->layout()->set("sum_price_sepa", $sum_price_sepa); $this->layout()->set("sum_price_gross", $sum_price_gross); $this->layout()->set("sum_price_sepa_gross", $sum_price_sepa_gross); $this->layout()->set("sum_credit_price", $sum_credit_price); $this->layout()->set("sum_credit_price_gross", $sum_credit_price_gross); $this->layout()->set("sum_voicecalls_price", $sum_voicecalls_price); $this->layout()->set("sum_voicecalls_price_gross", $sum_voicecalls_price_gross); $this->layout()->set("sum_voicecalls_price_sepa", $sum_voicecalls_price_sepa); $this->layout()->set("sum_voicecalls_price_sepa_gross", $sum_voicecalls_price_sepa_gross); } private function getPreparedFilter($filter) { $new_filter = []; if (array_key_exists("customer", $filter)) { if (array_key_exists("customer", $filter) && $filter["customer"]) { $kunde = $this->db()->escape(trim($filter['customer'])); if (!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = ""; $new_filter['add-where'] .= " AND (company like '%$kunde%' OR firstname like '%$kunde%' OR lastname like '%$kunde%' OR concat(firstname, ' ', lastname) like '%$kunde%' OR concat(lastname, ' ', firstname) like '%$kunde%')"; } } if(array_key_exists("address", $filter)) { if($filter["address"]) { $search = $this->db()->escape(trim($filter['address'])); if (!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = ""; $new_filter['add-where'] .= " AND (street like '%$search%' OR zip like '%$search%' OR city like '%$search%' OR country like '%$search%')"; } } if(array_key_exists("invoice_number", $filter) && $filter["invoice_number"]) { $new_filter["invoice_number%"] = trim($filter["invoice_number"]); unset($filter["invoice_number"]); } if(array_key_exists("invoice_date_from", $filter)) { if($filter["invoice_date_from"]) { $from = Layout::dateToInt($filter["invoice_date_from"]); $new_filter["invoice_date>="] = $from; } unset($filter["invoice_date_from"]); } if(array_key_exists("invoice_date_to", $filter)) { if($filter["invoice_date_to"]) { $to = Layout::dateToInt($filter["invoice_date_to"])." 23:59:59"; $new_filter["invoice_date<="] = $to; } unset($filter["invoice_date_to"]); } if (is_array($filter) && count($filter)) { foreach ($filter as $name => $value) { $new_filter[$name] = $value; } } return $new_filter; } protected function downloadInvoice() { $id = $this->request->id; if (!is_numeric($id) || !$id) { $this->layout()->setFlash("Rechnung nicht gefunden", "error"); $this->redirect("Invoice"); } $invoice = new Invoice($id); if (!$invoice->id) { $this->layout()->setFlash("Rechnung nicht gefunden", "error"); $this->redirect("Invoice"); } $pdf = $invoice->pdf; //var_dump($pdf, !$pdf);exit; //var_dump($pdf->name);exit; if(!$pdf || !$pdf->name) { $ifile = InvoiceFileModel::createFromInvoice($invoice); if(!$ifile) { $this->layout()->setFlash("Fehler beim PDF erstellen"); $this->redirect("Invoice"); } $pdf = $ifile->file; } $pdf_path = $pdf->getFullPath(); $filename = $pdf->filename; if(!file_exists($pdf_path)) { $this->layout()->setFlash("PDF-Datei nicht gefunden"); $this->redirect("Invoice"); } header('Content-Type: application/octet-stream'); header('Content-disposition: attachment; filename="'.$filename.'"'); header('Content-Transfer-Encoding: binary'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Content-Type: ' . mime_content_type($pdf_path)); header("Content-Length: " . filesize($pdf_path)); readfile($pdf_path); exit; } protected function downloadInvoiceCsv() { $id = $this->request->id; if (!is_numeric($id) || !$id) { $this->layout()->setFlash("Rechnung nicht gefunden", "error"); $this->redirect("Invoice"); } $invoice = new Invoice($id); if (!$invoice->id) { $this->layout()->setFlash("Rechnung nicht gefunden", "error"); $this->redirect("Invoice"); } if(!$invoice->positions) { $this->layout()->setFlash("Keine Rechnungspositionen vorhanden", "error"); $this->redirect("Invoice"); } $tpl = new Layout(); $tpl->setTemplate("Invoice/invoicepositions.csv"); $tpl->set("invoice", $invoice); $tpl->display(); //$csv = $tpl->render(); //echo $csv; exit; } protected function downloadInvoiceVoiceDetailsAction() { $id = $this->request->id; if (!is_numeric($id) || !$id) { $this->layout()->setFlash("Rechnung nicht gefunden", "error"); $this->redirect("Invoice"); } $invoice = new Invoice($id); if (!$invoice->id) { $this->layout()->setFlash("Rechnung nicht gefunden", "error"); $this->redirect("Invoice"); } $csv = "Startzeit;Abgehende Nummer;Zielnummer;Zone;Dauer;Kosten\n"; $total = 0; $destinations_cache = []; //var_dump($invoice->voicenumbers);exit; foreach($invoice->voicenumbers as $voicenumber) { $start_date = new DateTime($voicenumber->start_date); //$start_date->setTimezone(new DateTimeZone("Europe/Vienna")); $start_date->setTime(0,0,0); $end_date = new DateTime($voicenumber->end_date); //$end_date->setTimezone(new DateTimeZone("Europe/Vienna")); $end_date->setTime(23,59,59); $call_date_start = $start_date->format("Y-m-d"); foreach(VoiceCallHistoryModel::getVoiceCallHistoryAsEntity([ "voice_account" => $voicenumber->voicenumber, "start" => [ "from" => $start_date->getTimestamp(), "to" => $end_date->getTimestamp() ], "billable" => 1, ], null, 0, ["key" => "start", "order" => "ASC"]) as $call) { if(!$call->contract_id) continue; //$voiceplan = new Voiceplan($voicenumber->voiceplan_id); $voiceplan = VoiceplanModel::getFirst(["name" => $voicenumber->voiceplan]); if(!$voiceplan) { $this->log->warning(__METHOD__.": Voiceplan not found"); exit; } $number = $voicenumber->voicenumber; $dest_nummer = $call->destination; if (substr($dest_nummer, 0, 2) == "00") { $dest_nummer = substr($dest_nummer, 2); } if (substr($dest_nummer, 0, 1) == "+") { $dest_nummer = substr($dest_nummer, 1); } if (array_key_exists($dest_nummer, $destinations_cache)) { $destination = $destinations_cache[$dest_nummer]; } else { $destination = $voiceplan->getDestinationByNumber($dest_nummer); if (!$destination) { die("Destination für Zielrufnummer " . $call->destination . " nicht gefunden"); } $destinations_cache[$dest_nummer] = $destination; } //var_dump($destination); $zone = $destination->voiceplanzone; if (!$zone) { die("Keine Zone für Destination " . $dest_nummer . " gefunden"); } //var_dump($zone);exit; // inc_first - first minimumm duration to bill // inc - subsequent minimum duration to bill $inc_first = $zone->increment_first; $inc = $zone->increment; $billable_duration = $call->duration; if ($billable_duration <= 0) continue; // calculate price of first duration unit // then subtract first minimum duration from duration $sec_price = $zone->price / 60; $call_price = $inc_first * $sec_price; $billable_duration -= $inc_first; // calculate price of remaining duration and make sure to bill in full duration units if ($billable_duration > 0) { $multi = ceil($billable_duration / $inc); $call_price += ($multi * $inc) * $sec_price; } $csv .= '"'.$call->start.'";'; $csv .= '"'.$call->source.'"; '; $csv .= '"'.$call->destination.'"; '; $csv .= '"'.$zone->name.'"; '; $csv .= $call->duration.'; '; $csv .= '"'.str_replace(".",",", $call_price).'"'; $csv .= "\n"; $total += $call_price; } } header("Content-type: text/csv; charset=utf-8"); header('Content-disposition: attachment; filename="'.$invoice->invoice_number.'-egn.csv"'); echo $csv; exit; } protected function createJob() { $r = $this->request; //var_dump($r->get());exit; $task = $r->task; if($task != "make-invoice-pdf" && $task != "send-invoice-email") { $this->layout()->setFlash("Ungültiger Task", "error"); $this->redirect("Invoice"); } if(!$r->from_date || !$r->to_date) { $this->layout()->setFlash("Start- und Endddatum werden benötigt", "error"); $this->redirect("Invoice"); } try { $from_date = DateTime::createFromFormat("d.m.Y", $r->from_date, new DateTimeZone("Europe/Vienna")); $from_date->setTime(0,0,0); $to_date = DateTime::createFromFormat("d.m.Y", $r->to_date, new DateTimeZone("Europe/Vienna")); $to_date->setTime(23,59,59); } catch(Exception $e) { $this->layout()->setFlash("Von- oder Bisdatum ungültig", "error"); $this->redirect("Invoice"); } $job = InvoiceJobModel::create([ "task" => $task, "from_date" => $from_date->format("Y-m-d"), "to_date" => $to_date->format("Y-m-d"), ]); if(!$job->save()) { $this->layout()->setFlash("Fehler beim Speichern", "error"); $this->redirect("Invoice"); } $this->layout()->setFlash("Task erfolgreich gespeichert", "success"); $this->redirect("Invoice"); } protected function runInvoicingAction() { if(!$this->me->can("Billing")) { $this->redirect("Dashboard"); } $i = 0; $p = 0; $base_rows_bill = BillingModel::getInvoiceBaseData(['invoice_id' => null, "!vatgroup_id" => TT_VATGROUP_CREDIT]); $base_rows_credit = BillingModel::getInvoiceBaseData(['invoice_id' => null, "vatgroup_id" => TT_VATGROUP_CREDIT]); $base_rows = array_merge($base_rows_bill, $base_rows_credit); // get pairs of owner_id and billingaddress_id, so each will be its own invoice foreach ($base_rows as $base) { //var_dump($base);exit; $owner_id = $base["owner_id"]; $billingaddress_id = $base["billingaddress_id"]; $billing_type = $base["billing_type"]; $billing_delivery = $base["billing_delivery"]; $base_vatgroup_id = $base["vatgroup_id"]; $bill_positions = []; $credit_positions = []; $invoice_voicenumbers = []; $bill_filter = ["owner_id" => $owner_id, "billingaddress_id" => $billingaddress_id, "billing_type" => $billing_type, "billing_delivery" => $billing_delivery, "invoice_id" => null]; if($base_vatgroup_id == TT_VATGROUP_CREDIT) { $bill_filter["vatgroup_id"] = TT_VATGROUP_CREDIT; } else { $bill_filter["!vatgroup_id"] = TT_VATGROUP_CREDIT; } $billing_rows = BillingModel::search($bill_filter); if(!count($billing_rows)) continue; //var_dump($billing_rows);exit; //var_dump($owner_id, $billingaddress_id, $bills);exit; $invoice_data = []; $invoice_vatrate = 20; $price_total_sum = 0; foreach($billing_rows as $bill) { $vatgroup_id = $bill->vatgroup_id; $vatarea = $bill->vatarea; $invoice_vatrate = $bill->vatgroup->rates[$vatarea]->rate; $vatrate = $invoice_vatrate; $price = $bill->price; $price_total = $bill->price * $bill->amount; $price_gross = ($vatrate) ? $price_total + ($price_total / 100) * $vatrate : $price_total; $price_setup = $bill->price_setup; $price_setup_total = $bill->price_setup * $bill->amount; $price_setup_gross = ($vatrate) ? $price_setup_total + ($price_setup_total / 100) * $vatrate : $price_setup_total; $add_setup_position = ($price > 0 && $price_setup > 0); $is_setup_only = ($price < 0.00001 && ($price_setup > 0.00001 || $price_setup < -0.00001)); $price_total_sum += $price_gross + $price_setup_gross; $position_data = []; $position_data["billing_id"] = $bill->id; $position_data["contract_id"] = $bill->contract_id; $position_data["start_date"] = $bill->start_date; $position_data["end_date"] = $bill->end_date; $position_data["matchcode"] = $bill->matchcode; $position_data["product_id"] = $bill->product_id; $position_data["product_name"] = $bill->product_name; $position_data["product_info"] = $bill->product_info; $position_data["amount"] = $bill->amount; $position_data["billing_period"] = $bill->billing_period; $position_data["fibu_cost_account"] = $bill->vatgroup->rates[$vatarea]->account; $position_data["fibu_cost_account_legacy"] = $bill->vatgroup->rates[$vatarea]->legacy_account; $position_data["fibu_taxcode"] = $bill->vatgroup->rates[$vatarea]->taxcode; if ($is_setup_only) { $this->log->debug("Contract ID " . $bill->contract_id . " is setup only"); $position_data["price"] = $price_setup; $position_data["price_total"] = $price_setup_total; $position_data["price_gross"] = $price_setup_gross; $position_data["vatrate"] = $vatrate; $position_data["end_date"] = $position_data["start_date"]; } else { $position_data["price"] = $price; $position_data["price_total"] = $price_total; $position_data["price_gross"] = $price_gross; $position_data["vatrate"] = $vatrate; } $new_position = InvoicepositionModel::create($position_data); $bill_positions[] = $new_position; if ($add_setup_position) { $this->log->debug("Adding Setup Invoiceposition for Contract ID " . $bill->contract_id); $setup_data = $position_data; $setup_data["product_name"] = "Herstellungskosten " . $bill->product_name; $setup_data["product_info"] = ""; $setup_data["price"] = $price_setup; $setup_data["price_total"] = $price_setup * $bill->amount; $setup_data["price_gross"] = $price_setup_gross; $setup_data["vatrate"] = $vatrate; $setup_data["end_date"] = $setup_data["start_date"]; $setup_position = InvoicepositionModel::create($setup_data); $bill_positions[] = $setup_position; } /* * *************************************** * Get Voicenumber Billing to Billing row * *************************************** */ $voicebills = BillingVoicenumberModel::search(["billing_id" => $bill->id]); //var_dump($voicebills);exit; if (count($voicebills)) { $voice_start_date = reset($voicebills)->start_date; $voice_end_date = reset($voicebills)->end_date; $voiceplan = reset($voicebills)->voiceplan; $inc = reset($voicebills)->increment; $inc_first = reset($voicebills)->increment_first; $zoneId2ZoneName = []; $voice_rows = []; foreach ($voicebills as $voicebill) { $number = $voicebill->voicenumber; $zone_id = $voicebill->zone_id; $zone = $voicebill->zone; $call_count = $voicebill->call_count; $duration = $voicebill->duration; $price = $voicebill->price; $price_total = $voicebill->price_total; $zoneId2ZoneName[$zone_id] = $zone; if (!array_key_exists($number, $voice_rows)) { $voice_rows[$number] = []; } if (!array_key_exists($zone_id, $voice_rows[$number])) { $voice_rows[$number][$zone_id] = []; } if (!array_key_exists($price, $voice_rows[$number][$zone_id])) { $voice_rows[$number][$zone_id][$price] = []; $voice_rows[$number][$zone_id][$price]["call_count"] = 0; $voice_rows[$number][$zone_id][$price]["duration"] = 0; $voice_rows[$number][$zone_id][$price]["price_total"] = 0; } $voice_rows[$number][$zone_id][$price]["call_count"] += $call_count; $voice_rows[$number][$zone_id][$price]["duration"] = $duration; $voice_rows[$number][$zone_id][$price]["price_total"] = $price_total; } //var_dump($voice_rows);exit; $total_voice_price = 0; foreach ($voice_rows as $number => $zones) { $voicenumber_data = []; $voicenumber_data["voicenumber"] = $number; $voicenumber_data["start_date"] = $voice_start_date; $voicenumber_data["end_date"] = $voice_end_date; $voicenumber_data["voiceplan"] = $voiceplan; $voicenumber_data["increment"] = $inc; $voicenumber_data["increment_first"] = $inc_first; $invoice_voicenumber = InvoiceVoicenumberModel::create($voicenumber_data); $invoice_voicenumber->voicenumberzones = []; foreach ($zones as $zone_id => $prices) { foreach ($prices as $price => $row_values) { $zone_data = []; $zone_data["zone"] = $zoneId2ZoneName[$zone_id]; $zone_data["call_count"] = $row_values["call_count"]; $zone_data["duration"] = $row_values["duration"]; $zone_data["price"] = $price; $zone_data["price_total"] = $row_values["price_total"]; $zone_data["price_total_gross"] = $row_values["price_total"]; if ($invoice_vatrate) { $zone_data["price_total_gross"] = $row_values["price_total"] + (($row_values["price_total"] / 100) * $invoice_vatrate); } $zone_data["vatrate"] = $invoice_vatrate; $total_voice_price += $row_values["price_total"]; $voicenumber_zone = InvoiceVoicenumberZoneModel::create($zone_data); //var_dump($voicenumber_zone); $invoice_voicenumber->data->voicenumberzones[] = $voicenumber_zone; } } //var_dump($invoice_voicenumber);exit; $invoice_voicenumbers[] = $invoice_voicenumber; } $price_total_sum += $total_voice_price; //var_dump($invoice_voicenumbers);exit; $this->log->debug("Adding Voice Invoiceposition for Contract ID " . $bill->contract_id); $voice_data = $position_data; $voice_data["product_name"] = "Gesprächsgebühren zu " . $bill->product_name; $voice_data["product_info"] = null; $voice_data["matchcode"] = null; $voice_data["price"] = $total_voice_price; $voice_data["price_total"] = $total_voice_price; $voice_data["price_gross"] = $total_voice_price + (($total_voice_price / 100) * 20); $voice_data["start_date"] = $voice_start_date; $voice_data["end_date"] = $voice_end_date; $voice_position = InvoicepositionModel::create($voice_data); $voice_position->setOption("timerange_month_only", true); $bill_positions[] = $voice_position; } /*if($bill->price >= 0 || $bill->price_setup >= 0) { $bill_positions[] = InvoicepositionModel::create($position_data); } else { $credit_positions[] = InvoicepositionModel::create($position_data);; }*/ $invoice_data["owner_id"] = $owner_id; $invoice_data["billingaddress_id"] = $billingaddress_id; $invoice_data["customer_number"] = $bill->customer_number; $invoice_data["fibu_account_number"] = $bill->fibu_account_number; $invoice_data["fibu_payment_due"] = $bill->fibu_payment_due ?: 14; $invoice_data["fibu_payment_skonto"] = $bill->fibu_payment_skonto ?: 0; $invoice_data["fibu_payment_skonto_rate"] = $bill->fibu_payment_skonto_rate ?: 0; $invoice_data["sepa_date"] = $bill->sepa_date; $invoice_data["sepa_id"] = $bill->sepa_id; $invoice_data["sepa_last_date"] = $bill->sepa_last_date; $invoice_data["company"] = $bill->company; $invoice_data["firstname"] = $bill->firstname; $invoice_data["lastname"] = $bill->lastname; $invoice_data["street"] = $bill->street; $invoice_data["zip"] = $bill->zip; $invoice_data["city"] = $bill->city; $invoice_data["country"] = $bill->country; $invoice_data["email"] = $bill->email; $invoice_data["uid"] = $bill->uid; $invoice_data["billing_type"] = $billing_type; $invoice_data["billing_delivery"] = $billing_delivery; $invoice_data["bank_account_bank"] = $bill->bank_account_bank; $invoice_data["bank_account_owner"] = $bill->bank_account_owner; $invoice_data["bank_account_iban"] = $bill->bank_account_iban; $invoice_data["bank_account_bic"] = $bill->bank_account_bic; $invoice_data["total"] = 0; $invoice_data["total_gross"] = 0; $invoice_data["vatgroup_id"] = $vatgroup_id; //$invoice_data["total_vat"] = 0; $invoice_data["fibu_cost_area"] = $vatarea; $invoice_data["fibu_cost_account"] = $bill->vatgroup->rates[$vatarea]->account; $invoice_data["fibu_cost_account_legacy"] = $bill->vatgroup->rates[$vatarea]->legacy_account; $invoice_data["fibu_taxcode"] = $bill->vatgroup->rates[$vatarea]->taxcode; $invoice_data["tax_text"] = $bill->vatgroup->rates[$vatarea]->invoice_text; } /* // to not generate zero total invoices if($price_total_sum == 0.0000) { continue; }*/ //var_dump($invoice_data);exit; /* * ******************************* * Save invoice and add positions * ******************************* */ // create Invoice $invoice = InvoiceModel::create($invoice_data); $invoice->startTransaction(); try { if (!$invoice->save()) { var_dump($invoice); $invoice->rollbackTransaction(); die("Error saving Invoice"); } $total_net = 0; $total_gross = 0; //$total_vat = 0; foreach ($bill_positions as $position) { // on error: rollback transaction // add Invoice::id to Invoiceposition $position->invoice_id = $invoice->id; if(!$invoice_vatrate || !$position->vatrate) { $total_net += $position->price_total; $total_gross += $position->price_total; } else { //$total_vat += ($position->price_total / 100) * $position->vatrate; $total_net += $position->price_total; $total_gross += $position->price_gross; } // save Invoiceposition // first round price $position->price_gross = round($position->price_gross, 4); if (!$position->save()) { $invoice->rollbackTransaction(); die("Error saving Invoiceposition"); } // ad Invoice::id to Bill $bill = new Billing($position->billing_id); if (!$bill->id) { $invoice->rollbackTransaction(); die("Bill for Invoiceposition not found"); } $bill->invoice_id = $invoice->id; if (!$bill->save()) { $invoice->rollbackTransaction(); die("error saving invoice_id to bill"); } $p++; } $invoice->total = $total_net; $invoice->total_gross = round($total_gross, 4); //$invoice->total_vat = $total_vat; if (!$invoice->save()) { $invoice->rollbackTransaction(); die("Error saving totals in Invoice"); } if(count($invoice_voicenumbers)) { foreach($invoice_voicenumbers as $inv_vn) { $invnz_s = $inv_vn->voicenumberzones; unset($inv_vn->voicenumberzones); $inv_vn->invoice_id = $invoice->id; if(!$inv_vn->save()) { $invoice->rollbackTransaction(); die("Error saving InvoiceVoicenumber"); } foreach($invnz_s as $invnz) { $invnz->invoicevoicenumber_id = $inv_vn->id; if(!$invnz->save()) { $invoice->rollbackTransaction(); die("Error saving InvoiceVoicenumberZone"); } } } } // generate Invoice number $new_num = InvoiceModel::getNextInvoiceNUmber(); $invoice->invoice_number = $new_num; $invoice->invoice_date = date("U"); // voicenumbers //var_dump($invoice_voicenumbers);exit; if (!$invoice->save()) { $invoice->rollbackTransaction(); die("Error saving Invoice number and date"); } $baddress = new Address($invoice->billingaddress_id); if($baddress) { $baddress->last_invoice_date = date("U"); $baddress->save(); } } catch (Exception $e) { if ($invoice) { $invoice->rollbackTransaction(); } die("Error saving Invoice!\n"); } // commit transaction $invoice->commitTransaction(); $i++; // Create Invoice PDF // if billing_delivery == paper -> add to pdf collection // else -> send by email //var_dump($invoice); //exit; } $this->layout()->setFlash("$i Rechnungen mit $p Rechnungspositionen erstellt"); $this->redirect("Invoice"); } protected function manualExportBmd() { if(!$this->me->can("Billing")) { $this->redirect("Dashboard"); } //var_dump($this->request->get()); $csv_header = "\u{FEFF}satzart;konto;belegnr;belegdatum;zziel;skontopz;skontotage;buchsymbol;buchcode;prozent;steuercode;betrag;steuer;text;"; $csv_header .= "bank-iban-nr;bank-swiftcode;bank-mandatsid;bank-mandatsdatum;bank-mandatskz;bank-letztereinzug;zvsperre;bankeinzug;"; $csv_header .= "gkonto;kobetrag"; $csv_out = ""; //var_dump($filter);exit; $filter = [ "lock" => 1, "exported" => 0, ]; if($this->request->manual_invoice_date_from) { $date_from = Layout::dateToInt($this->request->manual_invoice_date_from); if($date_from) { $filter["invoice_date"] = ["from" => $date_from]; } } if($this->request->manual_invoice_date_to) { $date_to = Layout::dateToInt($this->request->manual_invoice_date_to); if($date_to) { $filter["invoice_date"] = ["to" => $date_to]; } } //var_dump($filter);exit; if(!ManualInvoiceModel::count($filter)) { $this->layout()->setFlash("Keine Rechnungen zum Exportieren gefunden."); $this->redirect("Invoice"); } foreach(ManualInvoiceModel::getAll($filter) as $invoice) { if($invoice->exported) { die("wtf"); } $billingaddress = new Address($invoice->billingaddress_id); if(!$billingaddress->id) { die("Billingaddresse für Rechnung {$invoice->invoice_number} not found"); } $kostentraeger = []; //var_dump($invoice->getProperty("positions")); //$vat_total_gross = 0; foreach($invoice->getProperty("positions") as $position) { if(!array_key_exists($position->fibu_cost_account, $kostentraeger)) { $kostentraeger[$position->fibu_cost_account] = 0; } //$kostentraeger[$position->position_group] += $position->price_gross; //$vat_total_gross += $position->price_gross - $position->price_total; $price = $position->price_total; /*if($position->discount) { $price -= ($price / 100) * $position->discount; }*/ if($invoice->total_discount) { $price -= ($price / 100) * $invoice->total_discount; } $kostentraeger[$position->fibu_cost_account] += $price; } $total_gross = $invoice->total_gross; /*if($invoice->total_discount) { $total_gross -= round(($total_gross / 100) * $invoice->total_discount, 4); }*/ $total = $invoice->total; /*if($invoice->total_discount) { $total -= round(($total / 100) * $invoice->total_discount, 4); }*/ if($invoice->total_gross) { $vatrate = 20; } if($invoice->total == $invoice->total_gross && $invoice->fibu_cost_area != "domestic") { $vatrate = "0"; } else { $vatrate = "20"; } $vat = $total_gross - $total; $vat *= -1; //$vat_total_gross *= -1; if($invoice->total < 0) { $buchsymbol = "GU"; } else { $buchsymbol = "AR"; } $fibu_account = $invoice->fibu_account_number; $buchungstext = "[".$invoice->customer_number."]"; if($invoice->company) { $buchungstext .= " ".$invoice->company; } elseif($invoice->firstname || $invoice->lastname) { $buchungstext .= " ".$invoice->firstname." ".$invoice->lastname; } $buchungstext = str_replace(["\n","\r", ";"], "", $buchungstext); $buchcode = "1"; $is_sepa = ($invoice->billing_type == "sepa"); $iban = ""; $bic = ""; $sepa_id = ""; $sepa_date = false; $last_invoice_date = false; $mandatskz = ""; if($is_sepa) { $iban = $invoice->bank_account_iban; $bic = $invoice->bank_account_bic; $sepa_id = "R".$fibu_account; if($billingaddress->sepa_date) { $sepa_date = new DateTime("@".$billingaddress->sepa_date); $sepa_date->setTimezone(new DateTimeZone("Europe/Vienna")); if($billingaddress->last_invoice_date) { $sepa_last_date = new DateTime("@".$billingaddress->last_invoice_date); $data["sepa_last_date"] = $sepa_last_date->format("Y-m-d"); $last_invoice_date = new DateTime("@".$billingaddress->last_invoice_date); $last_invoice_date->setTimezone(new DateTimeZone("Europe/Vienna")); if($last_invoice_date->format("Y-m-d") < $sepa_date->format("Y-m-d")) { $last_invoice_date = false; } } } $mandatskz = ($last_invoice_date ? "1" : "0"); $three_years_ago = new DateTime("now"); $three_years_ago->modify("-3 years"); if($mandatskz == "0") { while($sepa_date->format("Y-m-d") < $three_years_ago->format("Y-m-d")) { $sepa_date->modify("+1 year"); } } } $kost = $invoice->fibu_cost_account; $csv_out .= "0;"; $csv_out .= $fibu_account.";"; //$csv_out .= $invoice->fibu_cost_account.";"; $csv_out .= $invoice->invoice_number.";"; $csv_out .= date("d.m.Y", $invoice->invoice_date).";"; $csv_out .= ($invoice->fibu_payment_due === null) ? ";" : $invoice->fibu_payment_due.";"; $csv_out .= ($invoice->fibu_payment_skonto) ? $invoice->fibu_payment_skonto.";" : ";"; $csv_out .= ($invoice->fibu_payment_skonto_rate) ? $invoice->fibu_payment_skonto_rate.";" : ";"; $csv_out .= $buchsymbol.";"; $csv_out .= $buchcode.";"; $csv_out .= $vatrate.";"; $csv_out .= $invoice->fibu_taxcode.";"; $csv_out .= number_format($total_gross, 2, ",", "").";"; $csv_out .= number_format($vat, 2, ",", "").";"; $csv_out .= $buchungstext.";"; $csv_out .= $iban.";"; $csv_out .= $bic.";"; $csv_out .= $sepa_id.";"; $csv_out .= ($sepa_date ? $sepa_date->format("d.m.Y") : "").";"; $csv_out .= $mandatskz.";"; $csv_out .= ($last_invoice_date ? $last_invoice_date->format("d.m.Y") : "").";"; $csv_out .= ($is_sepa ? 0 : 10).";"; $csv_out .= ($is_sepa ? 1 : 0); if(count($kostentraeger) >= 2) { foreach($kostentraeger as $gkonto_num => $kobetrag) { $kobetrag_text = number_format($kobetrag, 2, ",", ""); $csv_out .= "\n1;;;;;;;;;;;;;;;;;;;;;;$gkonto_num;$kobetrag_text;"; } } ///var_dump($kostentraeger); $csv_out .= "\n"; } //exit; /*$this->layout()->setFlash("Export erfolgreich abgeschlossen", "success"); $this->redirect("Invoice");*/ header("Content-type: text/csv; charset=utf-8"); header('Content-disposition: attachment; filename="tt-mrech-export-bmd-'.date('Y-m-d_H-i-s').'.csv"'); echo $csv_header."\n".$csv_out; exit; } protected function exportBmdAction() { if(!$this->me->can("Billing")) { $this->redirect("Dashboard"); } /* * satzart;konto;gkonto;belegnr;belegdatum;buchsymbol;buchcode;prozent;steuercode;betrag;steuer;text;kost;bank-iban-nr;bank-swiftcode;bank-mandatsid;bank-mandatsdatum;bank-mandatskz;bank-letztereinzug;zvsperre;bankeinzug * 0;234941;40010;TEST-1;23.01.2024;AR;1;20;1;1,2;-0,2;RNTEST-01;;AT293828200003027919;RZSTAT2G282;KJ2813;13.01.2023;0;;0;1 * 0;234325;40010;TEST-2;23.01.2024;GU;1;20;1;-1,2;0,2;GSTEST-01;;;;;;;;10;0 */ //var_dump($this->request->type);exit; $type = "normal"; if($this->request->export_type == "bmd-credit") { $type = "credit"; } $csv_header = "\u{FEFF}satzart;konto;gkonto;belegnr;belegdatum;zziel;skontopz;skontotage;buchsymbol;buchcode;prozent;steuercode;betrag;steuer;text;kost;"; if($type == "credit") { $csv_header .= "extbelegnr"; } else { $csv_header .= "bank-iban-nr;bank-swiftcode;bank-mandatsid;bank-mandatsdatum;bank-mandatskz;bank-letztereinzug;zvsperre;bankeinzug"; } $csv_out = ""; $filter = []; if (is_array($this->request->filter)) { $filter = $this->request->filter; $_SESSION[MFAPPNAME . '-Invoice-filter'] = $filter; } else { if (array_key_exists(MFAPPNAME . '-Invoice-filter', $_SESSION) && count($_SESSION[MFAPPNAME . '-Invoice-filter'])) { $filter = $_SESSION[MFAPPNAME . '-Invoice-filter']; } } $this->layout->set("filter", $filter); $filter = $this->getPreparedFilter($filter); //$filter["bmd_export_date"] = null; if($type == "credit") { $filter["vatgroup_id"] = TT_VATGROUP_CREDIT; } else { $filter["!vatgroup_id"] = TT_VATGROUP_CREDIT; } //$filter["invoice_number"] = "RN2024-X033707"; //var_dump($filter);exit; if(!InvoiceModel::search($filter)) { $this->layout()->setFlash("Keine neuen Rechnungen seit letztem BMD-Export vorhanden."); $this->redirect("Invoice"); } foreach(InvoiceModel::search($filter) as $invoice) { if($invoice->bmd_export_date) { die("wtf"); } if($invoice->total_gross){ $vatrate = 20; } if($invoice->total == $invoice->total_gross && $invoice->fibu_cost_area != "domestic") { $vatrate = "0"; } else { $vatrate = "20"; } $vat = $invoice->total_gross - $invoice->total; $vat *= -1; if($invoice->total < 0) { $buchsymbol = "GU"; } else { $buchsymbol = "AR"; } $fibu_account = $invoice->fibu_account_number; $buchungstext = "[".$invoice->customer_number."]"; if($invoice->company) { $buchungstext .= " ".$invoice->company; } elseif($invoice->firstname || $invoice->lastname) { $buchungstext .= " ".$invoice->firstname." ".$invoice->lastname; } $buchungstext = str_replace(["\n","\r", ";"], "", $buchungstext); $buchcode = "1"; $is_sepa = ($invoice->billing_type == "sepa"); $iban = ""; $bic = ""; $sepa_id = ""; $sepa_date = false; $last_invoice_date = false; $mandatskz = ""; if($is_sepa) { $iban = $invoice->bank_account_iban; $bic = $invoice->bank_account_bic; $sepa_id = $invoice->sepa_id; if($invoice->sepa_date) { $sepa_date = new DateTime($invoice->sepa_date); if($invoice->sepa_last_date) { $last_invoice_date = new DateTime($invoice->sepa_last_date); if($last_invoice_date->format("Y-m-d") < $sepa_date->format("Y-m-d")) { $last_invoice_date = false; } } } $mandatskz = ($last_invoice_date ? "1" : "0"); $three_years_ago = new DateTime("now"); $three_years_ago->modify("-3 years"); if($mandatskz == "0") { while($sepa_date->format("Y-m-d") < $three_years_ago->format("Y-m-d")) { $sepa_date->modify("+1 year"); } } } if($type == "credit") { $buchsymbol = "ER"; $buchcode = "2"; if($invoice->fibu_supplier_number) { $this->layout()->setFlash("Lieferant ".$invoice->customer_number." - ".$invoice->company." ".$invoice->firstname." ".$invoice->lastname. " hat keine Lieferantennummer! Bitte nachtragen."); $this->redirect("Invoice"); } $fibu_account = $invoice->fibu_account_number; } $csv_out .= "0;"; $csv_out .= $fibu_account.";"; $csv_out .= $invoice->fibu_cost_account.";"; $csv_out .= $invoice->invoice_number.";"; $csv_out .= date("d.m.Y", $invoice->invoice_date).";"; $csv_out .= ($invoice->fibu_payment_due === null) ? ";" : $invoice->fibu_payment_due.";"; $csv_out .= ($invoice->fibu_payment_skonto) ? $invoice->fibu_payment_skonto.";" : ";"; $csv_out .= ($invoice->fibu_payment_skonto_rate) ? $invoice->fibu_payment_skonto_rate.";" : ";"; $csv_out .= $buchsymbol.";"; $csv_out .= $buchcode.";"; $csv_out .= $vatrate.";"; $csv_out .= $invoice->fibu_taxcode.";"; $csv_out .= number_format($invoice->total_gross, 2, ",", "").";"; $csv_out .= number_format($vat, 2, ",", "").";"; $csv_out .= $buchungstext.";"; $csv_out .= ";"; // kostenstelle if($type != "credit") { $csv_out .= $iban.";"; $csv_out .= $bic.";"; $csv_out .= $sepa_id.";"; $csv_out .= ($sepa_date ? $sepa_date->format("d.m.Y") : "").";"; $csv_out .= $mandatskz.";"; $csv_out .= ($last_invoice_date ? $last_invoice_date->format("d.m.Y") : "").";"; $csv_out .= ($is_sepa ? 0 : 10).";"; $csv_out .= ($is_sepa ? 1 : 0); } $csv_out .= "\n"; } /*$this->layout()->setFlash("Export erfolgreich abgeschlossen", "success"); $this->redirect("Invoice");*/ header("Content-type: text/csv; charset=utf-8"); if($type == "credit") { header('Content-disposition: attachment; filename="tt-prov-export-bmd-'.date('Y-m-d_H-i-s').'.csv"'); } else { header('Content-disposition: attachment; filename="tt-rech-export-bmd-'.date('Y-m-d_H-i-s').'.csv"'); } echo $csv_header."\n".$csv_out; exit; } public function createPDFs($limit = false) { $invoice_path_base = MFUPLOAD_FILE_SAVE_PATH."/".TT_INVOICE_SAVE_SUBFOLDER; $created = 0; foreach(InvoiceModel::search(["invoice_file" => false]) as $invoice) { if($limit && $created >= $limit) { return $created; } if(InvoiceFileModel::getFirst(["invoice_id" => $invoice->id])) { continue; } $pdf = $invoice->pdf; if(!$pdf || !$pdf->id || !$pdf->file_id) { $ifile = InvoiceFileModel::createFromInvoice($invoice); } if(!file_exists($ifile->file->getFullPath())) { $ifile = $invoice->createPdf(); } if(!$ifile) { echo "Could not create PDF for ".$invoice->invoice_number."\n"; } $created++; //echo "."; } //echo "\n"; return $created; } protected function sendInvoicesAction() { if(!$this->me->can("Billing")) { $this->redirect("Dashboard"); } $r = $this->request; $type = $r->type; /*if($type == "email") { return $this->sendEmailInvoices(); }*/ if($type == "paper") { return $this->printInvoices(); } $this->redirect("Invoice"); } /* * Gutschriften auch an Xinon */ public function _sendEmailInvoices($limit = false) { $sent = 0; $defer = 0; foreach(InvoiceModel::search(["billing_delivery" => "email", "date_delivered" => false]) as $invoice) { if($limit && $sent >= $limit) { return ["sent" => $sent, "defer" => $defer]; } $pdf = $invoice->pdf; if(!$pdf || !$pdf->name) { //echo "PDF für ".$invoice->invoice_number." noch nicht generiert\n"; $defer++; continue; } $pdf_file = false; try { $pdf_file = $pdf->getFullPath(); } catch (Exception $e) { $this->log->error(__METHOD__.": File for Invoice ".$invoice->id." not found"); continue; } if(!file_exists($pdf_file)) { $this->log->error(__METHOD__.": Datei ".$pdf->filename." nicht gefunden"); continue; } if($invoice->total == 0) { $this->log->info(__METHOD__.": Skipping ".$invoice->invoice_number." because total is zero"); $invoice->date_delivered = date("U"); $invoice->save(); continue; } if($invoice->total < 0) { // Gutschriften auch an Xinon $invoice->sendByEmail("billing@xinon.at"); } if(!$invoice->sendByEmail()) { $this->log->warning(__METHOD__.": Error sending ".$invoice->invoice_number." to ".$invoice->email); $invoice->date_delivered = date("U"); $invoice->save(); continue; } $invoice->date_delivered = date("U"); $invoice->save(); $sent++; } return ["sent" => $sent, "defer" => $defer]; } public function printInvoices() { if(!$this->me->can("Billing")) { $this->redirect("Dashboard"); } $start = $this->request->delivery_start_date; $end = $this->request->delivery_end_date; try { $start_date = DateTime::createFromFormat("d.m.Y", $start, new DateTimeZone("Europe/Vienna")); $start_date->setTime(0,0,0); $end_date = DateTime::createFromFormat("d.m.Y", $end, new DateTimeZone("Europe/Vienna")); $end_date->setTime(23,59,59); } catch(Exception $e) { $this->layout()->setFlash("Von- oder Bisdatum ungültig", "error"); $this->redirect("Invoice"); } if(!InvoiceModel::count(["billing_delivery" => "paper", "invoice_date>=" => $start_date->getTimestamp(), "invoice_date<=" => $end_date->getTimestamp()])) { $this->layout()->setFlash("Keine Rechnungen im angegebenen Zeitraum gefunden", "error"); $this->redirect("Invoice"); } $pdf_files = []; foreach(InvoiceModel::search(["billing_delivery" => "paper", "invoice_date>=" => $start_date->getTimestamp(), "invoice_date<=" => $end_date->getTimestamp()]) as $invoice) { $pdf = $invoice->pdf; if(!$pdf || !$pdf->name) { die("PDF für ".$invoice->invoice_number." noch nicht generiert\n"); } $pdf_file = $pdf->getFullPath(); if(!file_exists($pdf_file)) { die("Datei ".$pdf->filename." nicht gefunden\n"); } $pdf_files[] = $pdf_file; // send gutschriften to billing@xinon.at if($invoice->total < 0) { // Gutschriften auch an Xinon $invoice->sendByEmail("billing@xinon.at"); } } //var_dump($pdf_files);exit; if(!count($pdf_files)) { $this->layout()->setFlash("Fehler beim PDF erstellen: Keine PDFs zum zusammenführen", "error"); $this->redirect("Invoice"); } $output_path = MFUPLOAD_FILE_SAVE_PATH."/".TT_INVOICE_SAVE_SUBFOLDER; $output_filename = "print-invoices-".date("Y-m-d_H-i-s").".pdf"; $output_filepath = "$output_path/$output_filename"; //var_dump($pdf_files);exit; $i = 0; // Merge PDFs one after the other, otherwise the cmdline arguments could become too many for the shell to handle // Need to use a new output file name every time pdfunite runs // Cannot use first input file as output // So just swap 2 temp file names each round $first = true; $tmp_file1 = "$output_filepath.tmp1"; $tmp_file2 = "$output_filepath.tmp2"; $tmp_file = ""; foreach($pdf_files as $file) { if($i % 2 == 1) { $tmp_file = $tmp_file1; $src_file = $tmp_file2; } else { $tmp_file = $tmp_file2; $src_file = $tmp_file1; } if($first) { $pdf_unite_cmd = PDFUNITE_BIN_PATH." '$file' '$tmp_file'"; $first = false; } else { $pdf_unite_cmd = PDFUNITE_BIN_PATH." '$src_file' '$file' '$tmp_file'"; } shell_exec($pdf_unite_cmd); $i++; } rename($tmp_file, $output_filepath); unlink($tmp_file1); unlink($tmp_file2); if(!file_exists($output_filepath)) { $this->layout()->setFlash("Fehler beim PDFs zusammenführen: Ausgabedatei nicht gefunden", "error"); $this->redirect("Invoice"); } header('Content-Type: application/octet-stream'); header('Content-disposition: attachment; filename="'.$output_filename.'"'); header('Content-Transfer-Encoding: binary'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Content-Type: ' . mime_content_type($output_filepath)); header("Content-Length: " . filesize($output_filepath)); readfile($output_filepath); exit; } }