Jump to content

Rechnungen monatlich als CSV exportieren


Recommended Posts

Hallo liebe Community,

ich möchte für den Monat Mai meine Rechnungen aus dem Prestashop: 1.7.6.1 als CSV exportieren, um sie in meine Buchhaltungssoftware zu importieren.

Habe direkt in Prestashop nur die Möglichkeit gefunden die Rechnungen als PDF zu exportieren.

Über eine SQL Abfrage möchte ich dieses Aufgabe ungern lösen, da auch Laien, diesen Vorgang in Zukunft durchführen sollen.

Gibt es einen Bereich bei Prestashop diesen ich übersehen habe, oder funktioniert dies nur mit einem Modul?

Vielen Dank im Voraus

Liebe Grüße
Manuel

Link to comment
Share on other sites

  • 1 year later...

Ich habe jetzt mal für die Übernahme der Rechnungsdaten aus Prestashop 1.7.7.6 eine für mich praktikable halbautomatische Lösung gefunden. Das muss man sicherlich auf seine Buchhaltung abstimmen, aber wenn man viele Rechnungen im Monat hat, lohnt sich schon ein Import anstelle der händischen Buchung.

Ich nutze dafür das Prestashop System Backoffice und mit ein paar Handgriffen kann man da eine Export Funktion nach Datum recht bequem einbauen. 

Unter Bestellungen -> Rechnungen kann man ganz unten auf der Seite im Backoffice ein Rechnungsformular auswählen. Üblicherweise ist da invoice und invoice-b2b verfügbar. Man kann weitere Rechnungsformulare beliebig dazu nehmen, indem man z.B. das invoice.tpl in httpdocs/pdf einfach kopiert. Bei mir heisst es z.B. recht einfach invoice-csv. Damit kann man im Backoffice schon mal zwischen dem klassischen Rechnungsexport nach Datum unterscheiden, ob man das PDF Format will (jede Rechnung eine neue Seite) oder das CSV Format (alle Rechnungsdaten auf eine Seite). Da ich nicht allzu tief in die Programmierung eingreifen und einsteigen wollte, nutze ich für das CSV Format auch die PDF Engine. Das nur vorab.

Die Änderungen halten sich hierbei in Grenzen. 
Änderungen sind an den Klassen PDF.php und PDFGenerator.php im Order classes/pdf erforderlich.

PDFGenerator.php

    public function __construct($use_cache = false, $orientation = 'P')
    {
        if ($orientation=='L') parent::__construct($orientation, 'mm', 'A1', true, 'UTF-8', $use_cache, false);
        else parent::__construct($orientation, 'mm', 'A4', true, 'UTF-8', $use_cache, false);
        $this->setRTL(Context::getContext()->language->is_rtl);
    }

Hier benutze ich für das CSV Format zum Einen Landscape Einstellung anstelle von Porträt (also Querformat) und ausserdem ändere ich die Größe des PDF Dokuments von A4 auf A1. Das hängt ein bischen von der Anzahl der exportierten Felder ab, kann auch A2 reichen wenn man Rechnungs- und Lieferadresse nicht braucht. Wichtig ist, es darf kein Zeilenumbruch auftauchen. Wie gesagt - es ist ein Workaround. 

PDF.php

class PDFCore
{
    public $filename;
    public $pdf_renderer;
    public $objects;
    public $template;
    public $send_bulk_flag = false;
    public $makeCSV;
    public $makeCSVslips;
    public $csvDelim=',';

Hier sind zunächst 3 Variablen zur Steuerung definiert, makeCSV schaltet zwischen dem Standard Format und CSV um, makeCSVslips betrifft die Gutschriften. Da sind bei den Gutschriften weniger Informationsfelder vorhanden als bei den Rechnungen und csvDelim legt den Delimiter oder Feldtrennzeichen fest (hier Komma). Generell sollte man bei dem automatischen Import von Gutschriften eher vorsichtig sein. Die würde ich im Zweifel vielleicht doch lieber von Hand buchen. Sollten auch nicht übermässig viele sein, der Schwerpunkt liegt ja doch auf Rechnungen.

    public function __construct($objects, $template, $smarty, $orientation = 'P')
    {
        if (strcmp(Configuration::get('PS_INVOICE_MODEL'),'invoice-csv')==0) $this->makeCSV=true;
        else $this->makeCSV=false;
        if ($template==PDF::TEMPLATE_ORDER_SLIP) $this->makeCSVslips=true;
        else $this->makeCSVslips=false;
		
        if ($this->makeCSV) $this->pdf_renderer = new PDFGenerator((bool) Configuration::get('PS_PDF_USE_CACHE'), 'L');
        else $this->pdf_renderer = new PDFGenerator((bool) Configuration::get('PS_PDF_USE_CACHE'), $orientation);
        $this->template = $template;

Als Nächstes ist der Konstruktor anzupassen. Da wird makeCSV in Abhängigkeit vom Rechnungsformular (invoice oder invoice-csv) gesteuert und festgestellt, ob es sich um Rechnungen oder Gutschriften handelt (makeCSVslips). In Abhängigkeit von makeCSV wird dann der PDFGenerator entweder im klassischen P Format oder eben im Landscape initialisiert.

In der Funktion render(.....) ist der untere Teil anzupassen.

            if ($this->makeCSV) {
                $printData=$this->getAllOrderData();
                $this->pdf_renderer->createContent($printData);
            }
            else {
                $this->pdf_renderer->createHeader($template->getHeader());
                $this->pdf_renderer->createFooter($template->getFooter());
                $this->pdf_renderer->createPagination($template->getPagination());
                $this->pdf_renderer->createContent($template->getContent());
            }            
            $this->pdf_renderer->writePage();
            $render = true;

            unset($template);
            if ($this->makeCSV) break;
        }

        if ($render) {
            // clean the output buffer
            if (ob_get_level() && ob_get_length() > 0) {
                ob_clean();
            }

            return $this->pdf_renderer->render($this->filename, $display);
        }
    }

In Abhängigkeit von makeCSV werden die Footer,Header, etc. weggelassen. Die sind in der Liste sowieso nutzlos. Es geht rein um Text. Die Funktion getAllOrderData() stellt alle (im Datumsbereich ausgewählten) Rechnungen in Textform zusammen, jede Rechnung eine neue Zeile und die Felder getrennt mit dem festgelegtem Delimiter.

die Funktion getAllOrderData() ist ein wenig umfangreicher geworden durch die unterschiedlich vorhandenen oder auch benannten Feldern je nach Rechnung oder Gutschrift.

    public function getAllOrderData()
    {
        $delim=$this->csvDelim;
        $csvHeader="RE-NR,Datum,Uhrzeit,Referenz,Status,Versandart,Zahlungart,Währung,Gesamt inkl., Gesamt exkl.,Waren inkl., Waren exkl.,Versand inkl.,Versand exkl.,Versand Steuersatz,Verpackung inkl.,Verpackung exkl.";
        if (!$this->makeCSVslips) $csvHeader.=",Firma,VAT Nr.,Nachname,Vorname,Adresse1,Adresse2,PLZ,Stadt,Land,Telefon";
//      if (!$this->makeCSVslips) $csvHeader.=",Firma,VAT Nr.,Nachname,Vorname,Adresse1,Adresse2,PLZ,Stadt,Land,Telefon";
        $csvData='';
        foreach ($this->objects as $object) {
            $template = $this->getTemplateObject($object);
            if (!$template) continue;
            $csvData.=substr($template->getFilename(),0,-4).$delim;
            $csvData.=substr($object->date_add,0,-9).$delim;
            $csvData.=substr($object->date_add,11).$delim;
            if (!$this->makeCSVslips) $csvData.=$object->getOrder()->reference.$delim;
            else $csvData.="RE-".$object->id_order.$delim;
            if (!$this->makeCSVslips) {
                $csvData.=$object->getOrder()->current_state.$delim;
                $csvData.=(new Carrier($object->getOrder()->id_carrier))->name.$delim;
                $csvData.=$object->getOrder()->payment.$delim;
                $csvData.=Currency::getIsoCodeById($object->getOrder()->id_currency).$delim;
                $csvData.=$object->total_paid_tax_incl.$delim;
                $csvData.=$object->total_paid_tax_excl.$delim;
                $csvData.=$object->total_products_wt.$delim;
                $csvData.=$object->total_products.$delim;
                $csvData.=$object->total_shipping_tax_incl.$delim;
                $csvData.=$object->total_shipping_tax_excl.$delim;
                $csvData.=$object->getOrder()->carrier_tax_rate.$delim;
                $csvData.=$object->total_wrapping_tax_incl.$delim;
                $csvData.=$object->total_wrapping_tax_excl.$delim;
            }
            else {
                $csvData.=$delim.$delim.$delim.$delim.$delim.$delim;
                $csvData.=$object->total_products_tax_incl.$delim;
                $csvData.=$object->total_products_tax_excl.$delim;
                $csvData.=$object->total_shipping_tax_incl.$delim;
                $csvData.=$object->total_shipping_tax_excl.$delim;
                $csvData.=$delim.$delim.$delim;
            }
            if (!$this->makeCSVslips) {
                $invoice_address=$this->formatAddress(new Address((int) $object->getOrder()->id_address_invoice));
                $csvData.=$invoice_address;
//              $delivery_address=$this->formatAddress(new Address((int) $object->getOrder()->id_address_delivery));
//              $csvData.=$delivery_address;
            }
            $csvData.="<br>";
        }
        return $csvHeader.'<br />'.$csvData;
	}

Die Lieferadresse habe ich mal auskommentiert. Wer die Rechnungsadresse nicht braucht, kann das auch auskommentieren (dann aber auch oben im CSV-Header). Es wird noch ein Order-Status mit ausgeliesen, allerdings nur als Zahlenwert. Wer das in einen Text übersetzt haben möchte, muss das selbst machen. Das war mir jetzt zu aufwändig da im System zu wählen. Wahrscheinlich spielt er auch keine grosse Rolle, je nachdem.

    public function formatAddress(Address $address)
    {
        $delim=$this->csvDelim;
        $data="";
        $data.='"'.$address->company.'"'.$delim;
        $data.='"'.$address->vat_number.'"'.$delim;
        $data.='"'.$address->lastname.'"'.$delim;
        $data.='"'.$address->firstname.'"'.$delim;
        $data.='"'.$address->address1.'"'.$delim;
        $data.='"'.$address->address2.'"'.$delim;
        $data.='"'.$address->postcode.'"'.$delim;
        $data.='"'.$address->city.'"'.$delim;
        $data.='"'.$address->country.'"'.$delim;
        $data.='"'.$address->phone.'"'.$delim;
        return $data;
    }

Der Vollständigkeit halber auch noch die Adressformatierung. Als Unterfunktion da ja ggf. 2 Adressen ausgelesen werden können.

Bitte vor dem Ändern die üblichen Sicherheitsmassnahmen wie Backup, Kopien der php Dateien anlegen etc. Ich übernehme selbstverständlich keine Haftung. Auch nicht, dass es mit anderen Prestashop Versionen so läuft. Auch sollte man den Export der Daten auf Vollständigkeit und Plausibilität prüfen. 

So noch ein Hinweis zur Weiterverarbeitung. Im PDF ist das jetzt wenig nützlich und vermutlich auch nicht importierbar. Man kann aber in Acrobat jedes PDF auch als Textformat speichern (Datei -> Als Text speichern) und kann da dann als Dateiendung auch gleich CSV auswählen. Wenn man es in Excel oder OpenOffice importiert oder weiterbearbeiten will, wäre wichtig hier die Optionen "Werte in Hochkomma als Text" und die Sprache am Besten auf Englisch(USA) o.ä. zu stellen, damit beim Importiert auch das Zahlenformat (Punkt statt Komma) richtig eingelesen und erkannt wird. Die Adressfelder werden explizit in Hochkomma exportiert, weil Kunden hier auch das Komma verwenden könnten. Sonst zerschiesst es sporadisch das Format.

Die Reihenfolge der Felder kann man ja auch nach Belieben bei der Erzeugung in den PHP Dateien auf seine Buchhaltungslösung anpassen.
Wie gesagt möchte ich meine Arbeit hier nur teilen, weil der eine oder andere das vielleicht recht gut gebrauchen kann.

Das Handling ist recht einfach, weil man im Backoffice einfach nur das Rechnungsformat auf invoice oder invoice-csv umstellen kann. Ich weiss nicht, ob das Format vom angemeldeten Benutzer abhängig ist. Vermutlich nicht. Das bedeutet, dass man ggf. nicht zeitgleich Rechnungen drucken und Rechnungen exportieren sollte. Naja aber das ist glaube ich ein organisatorisches Luxusproblem. 😉

Viel Spass damit.

Link to comment
Share on other sites

Danke dem Moderator für die Freischaltung des Beitrags. Ich habe noch 2 Ergänzungen.

Der MWSt Satz ist nur für die Versandkosten angegeben - sonst müsste man die komplette Bestellung auf Positionsebene aufdröseln. Auch das war mir zu aufwändig. Wenn sichergestellt ist (durch die Art der Produkte), dass der MWSt Satz (19% z.B.) auf alle Bestellungen zutrifft, kann man vereinfacht diese 19% auch auf die Waren (Products) übernehmen. Da ich derzeit kein OSS nutze, weiss ich nicht genau, wie die Regeln für Versandkosten sind. Also ob da die Steuer des Ziellandes auch auf die Versandkosten anfällt. Eventuell muss man sich halt mit einer Tabelle behelfen und eine fette if-then-else Konstruktion einbauen. 

Es funktioniert doch auch wenn man zeitgleich Rechnungen einzeln druckt zu Bestellungen und Daten exportiert, wenn man das invoice.tpl einfach in invoice-csv.tpl kopiert. Meine ersten Versuche waren allerdings anders und da hatte ich aus dem invoice-csv.tpl Einiges gelöscht. Das ist aber gar nicht notwendig, das kann vollkommen identisch sein und dann kommen sich CSV und Einzeldruck auch nicht ins Gehege. Es sei denn, auch die Rechnungen zu den Bestellungen werden so gedruckt. Was meiner Meinung nach ein wenig schwierig ist, weil man hier nicht nach Uhrzeit wählen kann sondern nur nach Datum. Insofern denke ich eher nicht, dass Leute so arbeiten.

Link to comment
Share on other sites

Gestern den Import ausprobiert in Monkey Office (echt empfehlenswerte Buchhaltungslösung für Mac+Win) und funktioniert super. Bei den Gutschriften wäre ich vorsichtig. Eventuell nur die Liste als Vorlage für Buchungssätze nehmen (die "Slips" ...). Ich dachte, die 

else $csvData.="RE-".$object->id_order.$delim;

bezieht sich auch die zugehörige Rechnung - ist aber wohl nicht so. Schaut nur so aus als ob ...

Ich musste lediglich in OpenOffice noch die Kontonummern Soll/Haben hinzufügen und die Periode. Alle anderen Felder konnte man per Klick zuordnen. Coolste Importfunktion, die ich bisher gesehen habe. Respekt an den Entwickler von Monkey Office. 🙂

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...