25.5, 'SE' => 25, 'DK' => 25, 'DE' => 19, 'NL' => 21, 'BE' => 21,
'FR' => 20, 'AT' => 20, 'IT' => 22, 'ES' => 21, 'PT' => 23, 'PL' => 23,
'CZ' => 21, 'EE' => 24, 'LV' => 21, 'LT' => 21, 'IE' => 23);
$country = function_exists('WC') && WC()->countries ? WC()->countries->get_base_country() : 'FI';
return $rates[$country] ?? 25.5;
}
add_filter('usedkit_default_settings', function ($d) {
return $d + array('margin' => 'no', 'margin_rate' => '', 'margin_method' => 'item',
'margin_default' => 'yes');
});
add_filter('usedkit_save_settings', function ($out, $post) {
$rate = str_replace(',', '.', (string) ($post['margin_rate'] ?? ''));
return $out + array(
'margin' => !empty($post['margin']) ? 'yes' : 'no',
'margin_rate' => is_numeric($rate) ? (string) (float) $rate : '',
'margin_method' => ($post['margin_method'] ?? '') === 'global' ? 'global' : 'item',
'margin_default' => !empty($post['margin_default']) ? 'yes' : 'no',
);
}, 10, 2);
add_action('usedkit_settings_rows', function ($s) { ?>
|
|
get_meta('_usedkit_margin');
if ($own === '' && $product->get_parent_id()) {
$parent = wc_get_product($product->get_parent_id());
$own = $parent ? $parent->get_meta('_usedkit_margin') : '';
}
return $own === '' ? usedkit_settings()['margin_default'] === 'yes' : $own === 'yes';
}
function usedkit_cost($product) {
$c = $product->get_meta('_usedkit_cost');
if ($c === '' && $product->get_parent_id()) {
$parent = wc_get_product($product->get_parent_id());
$c = $parent ? $parent->get_meta('_usedkit_cost') : '';
}
return $c === '' ? null : (float) $c;
}
/** The report's month from ?month=YYYY-MM, else this month. */
function usedkit_margin_month() {
$m = (string) usedkit_view_arg('margin', 'month', 'usedkit_margin_csv');
return preg_match('/^\d{4}-\d{2}$/', $m) ? $m : gmdate('Y-m');
}
/* ---- the product's fields ---------------------------------------------------------- */
add_action('woocommerce_product_options_pricing', function () {
if (!usedkit_margin_on()) {
return;
}
global $product_object;
woocommerce_wp_text_input(array(
'id' => '_usedkit_cost', 'data_type' => 'price',
'label' => __('Purchase price', 'usedkit') . ' (' . get_woocommerce_currency_symbol() . ')',
'description' => __('What you paid for this item: the margin-scheme VAT is on the difference.', 'usedkit'),
'desc_tip' => true, 'value' => $product_object ? $product_object->get_meta('_usedkit_cost') : ''));
woocommerce_wp_select(array(
'id' => '_usedkit_margin', 'label' => __('Margin scheme', 'usedkit'),
'options' => array('' => __('Shop default', 'usedkit'), 'yes' => __('Yes', 'usedkit'),
'no' => __('No (normal VAT)', 'usedkit')),
'value' => $product_object ? $product_object->get_meta('_usedkit_margin') : ''));
woocommerce_wp_text_input(array(
'id' => '_usedkit_bought', 'type' => 'date', 'label' => __('Purchase date', 'usedkit'),
'description' => __('When you bought it; the date it was listed if empty. The global margin counts purchases in the period they were made.', 'usedkit'),
'desc_tip' => true, 'value' => $product_object ? $product_object->get_meta('_usedkit_bought') : ''));
});
add_action('woocommerce_admin_process_product_object', function ($product) {
if (!usedkit_margin_on() || !usedkit_product_form_ok($product)) {
return;
}
// phpcs:disable WordPress.Security.NonceVerification -- verified in usedkit_product_form_ok()
if (isset($_POST['_usedkit_cost'])) {
$cost = wc_format_decimal(sanitize_text_field(wp_unslash($_POST['_usedkit_cost'])));
$cost === '' ? $product->delete_meta_data('_usedkit_cost') : $product->update_meta_data('_usedkit_cost', $cost);
}
if (isset($_POST['_usedkit_bought'])) {
$d = sanitize_text_field(wp_unslash($_POST['_usedkit_bought']));
preg_match('/^\d{4}-\d{2}-\d{2}$/', $d) ? $product->update_meta_data('_usedkit_bought', $d)
: $product->delete_meta_data('_usedkit_bought');
}
if (isset($_POST['_usedkit_margin'])) {
$m = sanitize_key(wp_unslash($_POST['_usedkit_margin']));
in_array($m, array('yes', 'no'), true) ? $product->update_meta_data('_usedkit_margin', $m)
: $product->delete_meta_data('_usedkit_margin');
}
// phpcs:enable
});
/* No separately charged VAT on a margin-scheme item. */
foreach (array('woocommerce_product_get_tax_status', 'woocommerce_product_variation_get_tax_status') as $usedkit_hook) {
add_filter($usedkit_hook, function ($status, $product) {
return (usedkit_margin_on() && usedkit_is_margin_item($product)) ? 'none' : $status;
}, 10, 2);
}
/* ---- orders ---------------------------------------------------------------------------- */
/** The margin on each margin-scheme line, recorded as the order is placed: the figures
* the report uses, fixed at the time of sale. */
function usedkit_record_margin($item) {
if (!usedkit_margin_on()) {
return;
}
$product = $item->get_product();
if (!usedkit_is_margin_item($product)) {
return;
}
$qty = max(1, (int) $item->get_quantity());
$sale = (float) $item->get_total() + (float) $item->get_total_tax();
$cost = usedkit_cost($product);
$rate = usedkit_margin_rate();
$item->update_meta_data('_usedkit_margin_item', 'yes');
$item->update_meta_data('_usedkit_rate', $rate);
if ($cost !== null) {
$item->update_meta_data('_usedkit_cost', $cost * $qty);
$item->update_meta_data('_usedkit_margin_vat', usedkit_margin_vat($sale, $cost * $qty, $rate));
}
}
add_action('woocommerce_checkout_create_order_line_item', function ($item) {
usedkit_record_margin($item);
}, 10, 1);
/* Orders made another way -- in the admin, through the API -- get theirs when paid. */
add_action('woocommerce_order_status_changed', function ($order_id, $from, $to, $order) {
if (!usedkit_margin_on() || !in_array($to, array('processing', 'completed'), true)) {
return;
}
foreach ($order->get_items() as $item) {
if ($item->get_meta('_usedkit_margin_item') === '') {
usedkit_record_margin($item);
$item->save();
}
}
}, 10, 4);
function usedkit_order_has_margin_items($order) {
foreach ($order->get_items() as $item) {
if ($item->get_meta('_usedkit_margin_item') === 'yes') {
return true;
}
}
return false;
}
/** The wording an invoice for margin-scheme goods must carry (Art. 226(14)). */
function usedkit_margin_notice() {
return __('Margin scheme – second-hand goods. VAT is not shown separately.', 'usedkit');
}
add_action('woocommerce_email_after_order_table', function ($order, $plain = false) {
if (usedkit_order_has_margin_items($order)) {
echo $plain ? "\n" . esc_html(usedkit_margin_notice()) . "\n" : '' . esc_html(usedkit_margin_notice()) . '
';
}
}, 10, 2);
add_action('woocommerce_order_details_after_order_table', function ($order) {
if (usedkit_order_has_margin_items($order)) {
echo '' . esc_html(usedkit_margin_notice()) . '
';
}
});
/* ---- the report ------------------------------------------------------------------------ */
add_action('admin_menu', function () {
if (usedkit_margin_on()) {
add_submenu_page('woocommerce', __('Margin VAT', 'usedkit'), __('Margin VAT', 'usedkit'),
'manage_woocommerce', 'usedkit-margin', 'usedkit_margin_report_page');
}
}, 20);
/** The margin-scheme lines of paid orders in [from, to): one row each, refunds off. */
function usedkit_margin_lines($from, $to) {
$orders = wc_get_orders(array('limit' => -1, 'status' => array('processing', 'completed'),
'date_paid' => strtotime($from . ' 00:00:00') . '...' . (strtotime($to . ' 00:00:00') - 1)));
$lines = array();
foreach ($orders as $order) {
foreach ($order->get_items() as $item) {
if ($item->get_meta('_usedkit_margin_item') !== 'yes') {
continue;
}
$sale = (float) $item->get_total() + (float) $item->get_total_tax();
$refunded = (float) $order->get_total_refunded_for_item($item->get_id());
$cost = $item->get_meta('_usedkit_cost');
$paid = $order->get_date_paid();
$lines[] = array(
'order' => $order->get_order_number(), 'date' => $paid ? $paid->date('Y-m-d') : '',
'item' => $item->get_name(), 'sale' => round($sale - $refunded, 2),
'cost' => $cost === '' ? null : (float) $cost,
'rate' => (float) ($item->get_meta('_usedkit_rate') ?: usedkit_margin_rate()));
}
}
return $lines;
}
/**
* Margin-scheme purchases made in [from, to): what the global method sets against the
* period's sales (Art. 318: total supplies less total purchases of the period), whether
* or not the items have sold. The purchase date is the product's own, else the day it
* was listed. Items without a purchase price are returned with cost null.
*/
function usedkit_margin_purchases($from, $to) {
$out = array();
$products = wc_get_products(array('limit' => -1, 'status' => array('publish', 'private', 'draft'),
'type' => array('simple', 'variable')));
foreach ($products as $p) {
if (!usedkit_is_margin_item($p)) {
continue;
}
$day = $p->get_meta('_usedkit_bought');
if ($day === '' && $p->get_date_created()) {
$day = $p->get_date_created()->date('Y-m-d');
}
if ($day === '' || $day < $from || $day >= $to) {
continue;
}
$out[] = array('id' => $p->get_id(), 'date' => $day, 'item' => $p->get_name(), 'cost' => usedkit_cost($p));
}
return $out;
}
function usedkit_margin_report_page() {
if (!current_user_can('manage_woocommerce')) {
return;
}
$month = usedkit_margin_month();
$span = absint(usedkit_view_arg('margin', 'span', 'usedkit_margin_csv')) === 3 ? 3 : 1;
$from = $month . '-01';
$to = gmdate('Y-m-d', strtotime($from . " +$span month"));
$carry = (float) str_replace(',', '.', (string) usedkit_view_arg('margin', 'carry'));
$s = usedkit_settings();
$lines = usedkit_margin_lines($from, $to);
$rate = usedkit_margin_rate();
if ($s['margin_method'] === 'global') {
// The period's sales against the period's purchases, sold or not.
$bought = usedkit_margin_purchases($from, $to);
$missing = array_filter($bought, function ($b) { return $b['cost'] === null; });
$rows = array_merge(array_map(function ($l) { return array($l['sale'], 0.0); }, $lines),
array_map(function ($b) { return array(0.0, (float) $b['cost']); },
array_filter($bought, function ($b) { return $b['cost'] !== null; })));
} else {
$missing = array_filter($lines, function ($l) { return $l['cost'] === null; });
$rows = array_map(function ($l) { return array($l['sale'], $l['cost']); },
array_filter($lines, function ($l) { return $l['cost'] !== null; }));
}
$calc = usedkit_margin_period($rows, $rate, $s['margin_method'], $carry); ?>