From d3076e9a0d0b3c7d1afe0cfa42c593e38f4edd25 Mon Sep 17 00:00:00 2001
From: Reinhold Kainhofer <reinhold@kainhofer.com>
Date: Thu, 30 Apr 2015 17:17:16 +0200
Subject: [PATCH] Implement invoice numbers for wpo wcpdf invoices and
 packaging slips

---
 TODO                            |  15 +++
 assets/js/ordernumber-config.js |  14 +++
 library/ordernumber_helper.php  |  36 ++++++-
 ordernumbers_woocommerce.php    | 165 ++++++++++++++++++++++++++------
 4 files changed, 202 insertions(+), 28 deletions(-)

diff --git a/TODO b/TODO
index 773ac61..5509c5d 100644
--- a/TODO
+++ b/TODO
@@ -38,3 +38,18 @@ woocommerce_new_order', $order_id );
 Order created in BackEnd:
 Created: wp_insert_post => Assign placeholder (no address yet)
 Modified: 
+
+
+TODO:
+Check order/invoice numbers for renewals: https://wordpress.org/support/topic/subscription-renewal-duplicate-invoice-number?replies=6#post-6138110
+
+
+
+
+INVOICES:
+=========
+
+woocommerce-pdf-invoices-packing-slips:
+-) Uses order_meta: _wcpdf_invoice_number => Set it manually to prevent the plugin from using its own counter
+-) Calls filter wpo_wcpdf_invoice_number($invoice_number, $order_number, $order_id, $order_data)  to format the invoice number. 
+-) Triggers before PDF creation: wp_wcpdf_before_pdf, wpo_wcpdf_process_order_ids, wpo_wcpdf_process_template_order($template_type, $order_id)
diff --git a/assets/js/ordernumber-config.js b/assets/js/ordernumber-config.js
index bd8421a..6d11565 100644
--- a/assets/js/ordernumber-config.js
+++ b/assets/js/ordernumber-config.js
@@ -7,9 +7,23 @@ jQuery( function ( $ ) {
 		if ($(this).is(':checked')) {
 			$('#ordernumber_format').closest('tr').show();
 			$('#ordernumber_global').closest('tr').show();
+			$('#ordernumber-countertable-ordernumber').closest('tr').show();
 		} else {
 			$('#ordernumber_format').closest('tr').hide();
 			$('#ordernumber_global').closest('tr').hide();
+			$('#ordernumber-countertable-ordernumber').closest('tr').hide();
+		}
+	}).change();
+
+	$('input#customize_invoice').change(function() {
+		if ($(this).is(':checked')) {
+			$('#invoice_format').closest('tr').show();
+			$('#invoice_global').closest('tr').show();
+			$('#ordernumber-countertable-invoice').closest('tr').show();
+		} else {
+			$('#invoice_format').closest('tr').hide();
+			$('#invoice_global').closest('tr').hide();
+			$('#ordernumber-countertable-invoice').closest('tr').hide();
 		}
 	}).change();
 
diff --git a/library/ordernumber_helper.php b/library/ordernumber_helper.php
index ecfb0f9..6b92639 100644
--- a/library/ordernumber_helper.php
+++ b/library/ordernumber_helper.php
@@ -39,6 +39,14 @@ class OrdernumberHelper {
 		$this->registerCallback ("setupDateTimeReplacements", array($this, "setupDateTimeReplacements"));
 	}
 	
+	static function getHelper() {
+		static $helper = null;
+		if (!$helper) {
+			$helper = new OrdernumberHelper();
+		}
+		return $helper;
+    }
+	
 	function getStyle($key) {
 		if (isset($this->_styles[$key])) {
 			return $this->_styles[$key];
@@ -388,7 +396,7 @@ class OrdernumberHelper {
 	public function counter_modification_create_table($type, $counters) {
         $html=array();
         $html[] = "<img src='" . $this->urlPath ('images', 'loading.gif') . "' class='ordernumber-loading' />";
-        $html[] = "<table class=\"ordernumber-countertable " . $this->getStyle('counter-table-class') . "\" " . $this->getStyle('counter-table-style') . ">";
+        $html[] = "<table class=\"ordernumber-countertable " . $this->getStyle('counter-table-class') . "\" " . $this->getStyle('counter-table-style') . " id='ordernumber-countertable-" . $type . "'>";
         $html[] = "<thead>";
         $html[] = "	<tr>";
         $html[] = "		<th class='counter_format'>" . $this->__ ('PLG_ORDERNUMBER_COUNTERLIST_HEADER_COUNTER')."</th>";
@@ -560,4 +568,30 @@ var ajax_ordernumber = ' . json_encode($json) . ';
 		return $js;
 	}
 	
+	public function ajax_counter_delete($type, $counter) {
+		// TODO: Check if counter value has changed meanwhile
+		$json = array('action' => 'delete_counter', 'success' => 0);
+		$json['success'] = $this->deleteCounter($type, $counter);
+		return $json;
+	}
+
+	public function ajax_counter_add ($type, $counter, $value) {
+		// TODO: Check if counter value has changed meanwhile
+		$json = array('action' => 'add_counter', 'success' => 0);
+		if ($this->getCounter($type, $counter, -1) != -1) {
+			// Counter already exists => error message
+			$json['error'] = sprintf($this->__('Counter "%s" already exists, cannot create again.'), $counter);
+		} else {
+			$json['success'] = $this->setCounter($type, $counter, $value);
+			$json['row']  = $this->counter_modification_create_row($type, $counter, $value);
+		}
+		return $json;
+	}
+	
+	public function ajax_counter_set ($type, $counter, $value) {
+		$json = array('action' => 'set_counter', 'success' => 0);
+		$json['success'] = $this->setCounter($type, $counter, $value);
+		$json['row']  = $this->counter_modification_create_row($type, $counter, $value);
+		return $json;
+	}
 }
diff --git a/ordernumbers_woocommerce.php b/ordernumbers_woocommerce.php
index d6b2c6c..656aa22 100644
--- a/ordernumbers_woocommerce.php
+++ b/ordernumbers_woocommerce.php
@@ -64,9 +64,11 @@ class OpenToolsOrdernumbers {
 
 		// When a new order is created, we immediately assign the order number:
 		add_action( 'wp_insert_post', array(&$this, 'check_assignNumber'), 10, 3);
-// 		add_action( 'save_post', array(&$this, 'check_assignNumber'), 10, 3);
 		// The filter to actually return the order number for the given order
 		add_filter ('woocommerce_order_number', array(&$this, 'get_ordernumber'), 10, 2/*<= Also get the order object! */);
+		
+		
+		$this->thirdparty_wpo_wcpdf_init();
 	}
 	
 
@@ -101,8 +103,7 @@ class OpenToolsOrdernumbers {
 			),
 			array(
 				'title'		=> $this->helper->__( 'Use global counter'),
-				'desc' 		=> $this->helper->__( 'A global counter is independent of the number format and will never reset. A non-global counter runs within the number format and will start from the inital value whenever any of the variables used in the format changes (to be precise: a new counter will be used, so it is possible to have multiple counters running in parallel).'),
-				'desc_tip'	=> true,
+				'desc' 		=> $this->helper->__( 'A global counter never resets. Non-global counters run within each number format and reset whenever any variable changes.'),
 				'id' 		=> 'ordernumber_global',
 				'type' 		=> 'checkbox',
 				'default'	=> 'no',
@@ -117,6 +118,51 @@ class OpenToolsOrdernumbers {
 			),
 			array( 'type' => 'sectionend', 'id' => 'ordernumber_options' ),
 			
+			
+			/**
+			 * Invoice number settings 
+			 */
+			
+			array(
+				'name' 		=> $this->helper->__( 'Advanced Invoice Numbers'),
+				'desc'		=> $this->helper->__('This plugin currently supports modifying the invoice number formats of the following invoicing plugins: <a href="https://wordpress.org/plugins/woocommerce-pdf-invoices-packing-slips/">WooCommerce PDF Invoices & Packing Slips</a>'),
+				'type' 		=> 'title',
+				'id' 		=> 'invoice_options'
+			),
+			
+			array(
+				'name' 		=> $this->helper->__( 'Customize Invoice Numbers'),
+				'desc' 		=> $this->helper->__( 'Check to use custom invoice numbers rather than the default format of your invoicing plugin.'),
+				'id' 		=> 'customize_invoice',
+				'type' 		=> 'checkbox',
+				'default'	=> 'no'
+			),
+			array(
+				'title'		=> $this->helper->__( 'Invoice number format'),
+				'desc' 		=> $this->helper->__( 'The format for the invoice numbers (variables can be entered as [...], the counter is indicated by the #). To use a different counter name than displayed, put the custom counter name after a |, e.g. "[year]-[month]/#|[year]" to use the month in the invoice number, but reset the counter only yearly. Advanced settings for the counter can be added as [#####:start/step], e.g. [#:100] to start new counters at 100, or [#/5] to increment the counter by 5. The number of # in the format determine how many digits are shown at least, e.g. [########] will always show at least 8 digits for the counter, e.g. 00000014.'),
+				'desc_tip'	=> true,
+				'id' 		=> 'invoice_format',
+				'default'	=> '#',
+				'type' 		=> 'text',
+				'css'		=> 'width: 100%',
+			),
+			array(
+				'title'		=> $this->helper->__( 'Use global counter'),
+				'desc' 		=> $this->helper->__( 'A global counter never resets. Non-global counters run within each number format and reset whenever any variable changes.'),
+				'id' 		=> 'invoice_global',
+				'type' 		=> 'checkbox',
+				'default'	=> 'no',
+			),
+			array(
+				'name' 		=> $this->helper->__( 'All invoice number counters'),
+				'desc'		=> $this->helper->__( 'View and modify the current counter values. The counter value is the value used for the previous number. All changes are immediately applied!'),
+				'desc_tip'	=> true,
+				'id' 		=> 'ordernumber_counters',
+				'type' 		=> 'ordernumber_counters',
+				'nrtype' 	=> 'invoice',
+			),
+			array( 'type' => 'sectionend', 'id' => 'invoice_options' ),
+			
 			// TODO: customize order password, and other numbers!
 
 			array(
@@ -131,19 +177,16 @@ class OpenToolsOrdernumbers {
 			),
 			array( 'type' => 'sectionend', 'id' => 'ordernumber_variables' ),
 
-			array(
-				'name' 		=> $this->helper->__( 'Current Counters'),
-				'type' 		=> 'title',
-				'id' 		=> 'ordernumber_counters'
-			),
-			array( 'type' => 'sectionend', 'id' => 'ordernumber_counters' ),
-
 		);
 		// Default options
 
 		add_option ('customize_ordernumber', 'no');
 		add_option ('ordernumber_format',    "#");
 		add_option ('ordernumber_global',    'no');
+
+		add_option ('customize_invoice', 'no');
+		add_option ('invoice_format',    "#");
+		add_option ('invoice_global',    'no');
 		
  		add_option ('ordernumber_variables',  array());
 	}
@@ -294,30 +337,17 @@ class OpenToolsOrdernumbers {
 	 */
 	
 	public function counter_delete_callback() {
-		$json = array('action' => 'delete_counter', 'success' => 0);
-		$json['success'] = $this->helper->deleteCounter($_POST['nrtype'], $_POST['counter']);
+		$json = $this->helper->ajax_counter_delete($_POST['nrtype'], $_POST['counter']);
 		wp_send_json($json);
 	}
 
 	public function counter_add_callback () {
-		$type = $_POST['nrtype'];
-		$format = $_POST['counter'];
-		$value = isset($_POST['value'])?$_POST['value']:"0";
-		$json = array('action' => 'add_counter', 'success' => 0);
-		if ($this->helper->getCounter($type, $format, -1) != -1) {
-			// Counter already exists => error message
-			$json['error'] = sprintf($this->helper->__('Counter "%s" already exists, cannot create again.'), $format);
-		} else {
-			$json['success'] = $this->helper->setCounter($type, $format, $value);
-			$json['row']  = $this->helper->counter_modification_create_row($type, $format, $value);
-		}
+		$json = $this->helper->ajax_counter_add($_POST['nrtype'], $_POST['counter'], isset($_POST['value'])?$_POST['value']:"0");
 		wp_send_json($json);
 	}
 	
 	public function counter_set_callback () {
-		$json = array('action' => 'set_counter', 'success' => 0);
-		$json['success'] = $this->helper->setCounter($_POST['nrtype'], $_POST['counter'], $_POST['value']);
-		$json['row']  = $this->helper->counter_modification_create_row($_POST['nrtype'], $_POST['counter'], $_POST['value']);
+		$json = $this->helper->ajax_counter_set($_POST['nrtype'], $_POST['counter'], $_POST['value']);
 		wp_send_json($json);
 	}
 	
@@ -460,6 +490,15 @@ class OpenToolsOrdernumbers {
 		return $number;
 	}
 
+	function get_or_create_number($orderid, $order, $type = 'ordernumber') {
+		$stored_number = get_post_meta( $orderid, $this->ordernumber_meta.$type, true);
+		if (!empty($stored_number)) {
+			return $stored_number;
+		} else {
+			return $this->generateNumber($orderid, $order, $type);
+		}
+	}
+	
 	/** 
 	 * The generic function to retrieve a particular number
 	 */
@@ -479,9 +518,81 @@ class OpenToolsOrdernumbers {
 	
 	/**
 	 * Callback function for Woocommerce to retrieve the ordernumber for an order
+	 * The hook to customize order numbers (requests the order number from the database; 
+	 * creates a new ordernumber if no entry exists in the database)
 	 */
 	function get_ordernumber($orderid, $order) {
-		return $this->get_number($orderid, $order);
+		return $this->get_number($orderid, $order, 'ordernumber');
 	}
+
+	/** ************************************************************
+	 *  Support for WPO WooCommerce PDF Invoices and Packaging Slips
+	 ** ************************************************************
+	 *
+	 *  - Invoice numbers are stored in the _wcpdf_invoice_number post meta
+	 *  - the filter wpo_wcpdf_invoice_number($invoice_number, $order_number, $order_id, $order_data) is called 
+	 *    to format the (existing) invoice number retrieved from that post meta
+	 *  - The action wpo_wcpdf_process_template_order($template_type, $order_id) is called right before
+	 *    the invoice is created. There we can already set the _wcpdf_invoice_number post meta with our own value
+	 */
+	 
+	/**
+	 * Initialize support for WPO WooCommerce PDF Invoices and Packaging Slips
+	 */
+	protected function thirdparty_wpo_wcpdf_init() {
+		// TODO: Wheck whether the woocommerce-pdf-invoices-packing-slips plugin is installed at all
+		add_filter ('wpo_wcpdf_invoice_number', array($this, 'thirdparty_wpo_wcpdf_invoice_number'), 30, 4);
+		add_action ('wpo_wcpdf_process_template_order', array($this, 'thirdparty_wpo_wcpdf_create_number'), 10, 2);
+		// Disable the invoice number-related controls in the config of the other plugin
+		add_action ('woocommerce_page_wpo_wcpdf_options_page', array($this, 'thirdparty_wpo_wcpdf_remove_options'));
+	}
+	 
+	/**
+	 * Support for WPO WooCommere PDF Invoices and Packaging Slips
+	 * Filter to return the invoice number => simply return the first argument unchanged (was already 
+	 * created in the correct format, no need to format it now again) 
+	 */
+	function thirdparty_wpo_wcpdf_invoice_number($invoice_number, $order_number, $order_id, $order_data) {
+		$nr = $this->get_number($order_id, $order_data, 'invoice');
+		if ($nr == $order_id) {
+			// No number was found, so the default is the order id => reset to invoice number
+			return $invoice_number;
+		} else {
+			return $nr;
+		}
+	}
+	/**
+	 * The action to actually create the number and store it as post meta with the order 
+	 */
+	function thirdparty_wpo_wcpdf_create_number($type, $orderid) {
+		if ($type=='invoice' && (get_option('customize_'.$type, 'no')!='no') ) {
+			$_of = new WC_Order_Factory();  
+			$order = $_of->get_order($orderid);
+			$number = $this->get_or_create_number($orderid, $order, $type);
+			// TODO: Store the invoice number counter in _wcpdf_invoice_number and the custom invoice 
+			// number in the opentools meta, because the plugin assumes the number to be numeric...
+			update_post_meta( $orderid, '_wcpdf_invoice_number', $number );
+		}
+	}
+	
+	/** 
+	 * The action that is called for the WPO WCPDF invoice plugin only, when the options page is loaded.
+	 * If this plugin is enabled and invoice numbers are configured, we simply remove all invoice number-specific
+	 * settings, because this plugin will be responsible....
+	 */
+	function thirdparty_wpo_wcpdf_remove_options() {
+		global $wp_settings_fields;
+		if (get_option('customize_invoice', 'no')!='no') {
+			$wp_settings_fields['wpo_wcpdf_template_settings']['invoice']['display_number']['title'] = $this->helper->__('Display invoice number');
+			$wp_settings_fields['wpo_wcpdf_template_settings']['invoice']['display_number']['args']['description'] = $this->helper->__('The <a href="admin.php?page=wc-settings&tab=checkout&section=ordernumber">Open Tools Ordernumber plugin</a> has invoice numbers enabled and will generate invoice numbers for this plugin.' );
+			unset($wp_settings_fields['wpo_wcpdf_template_settings']['invoice']['next_invoice_number']);
+			unset($wp_settings_fields['wpo_wcpdf_template_settings']['invoice']['invoice_number_formatting']);
+		} else {
+			$wp_settings_fields['wpo_wcpdf_template_settings']['invoice']['display_number']['args']['description'] = $this->helper->__('To let the Open Tools ordernumber plugin create invoice numbers with your desired format, please enable invoices in <a href="admin.php?page=wc-settings&tab=checkout&section=ordernumber">that plugin\'s configuration page</a>.' );
+		}
+	}
+	
+	
+	
  
 }
-- 
GitLab