D7.21 , DCommerce 1.5, Rules 2.2, Address Field 1.0-beta3
Well I built my first module. It populates a field in Address Field with selectable NC counties. It works well, albeit my code is likely ugly (having never written a module and no real experience in coding).
The issue: NC Dept. of Revenue says that a sales tax is applied to an order based on the buyers location not the sellers. Sales taxes are county based. In order to apply NC sales tax correctly, the checkout process needs to have the counties available in order to apply a rule so that the appropriate sales tax rate is levied. (thus the module).
The problem is I can't write the rule to work correctly. This is what I have.
Data selector: commerce-line-item:order
Address: billing address
Address component: "sub-administrative area (District)" (a county)
Operator: equals
Value: a list of counties for the appropriate tax rate.
I have tried the list as one item per line. I also tried it as a comma seperated value. Would pipes work? Can the value not be a list? I also tried contains as the operator. I can get it to work against a single item in the value i.e. Mecklenburg county (the only county with its own tax rate) I suppose I could write a hundred rules one for each county, but was really trying to avoid that.
I was hoping I could compare the county the buyer selects against the list of counties for a given tax rate. Is that not possible? This will also have bearing on my setting up shipping where I am wanting to compare the buyers zip code against a range of zip codes.
Any suggestions?
Anyone want to play with my module in case you have a site in NC selling within NC and needing to apply sales tax?

Comments
Solution
So I figured out how to do this. The rule is a "and/or" type. For anyone else, like me, who have not written rules, here is how I did it.
Required by Drupal Commerce.
In order to get the NC Counties field to work, these modules are required.
To start at the very beginning of creating the rule I added a tax at admin/commerce/config/taxes. After adding the tax, in the table showing taxes, under the Oprerations column, choose configure component. This will take you to admin/config/workflow/rules/components/manage/your component associated with the tax created.
This page has three sections; conditions, actions and settings. At the bottom of the conditions section there are three choices +Add condition, +Add or and +Add and. Choose Add condition. On the next screen, in the drop down for "Select the condition to add", choose Order address component comparison.
The next screen has five sections; Order, Address, Address component, Operator and Value.
Under Order, choose commerce-line-item:order.
Under Address, choose Billing information address.
Under Address component, choose Administrative area (State/Province).
Under operator, choose equals
Under Value, fill in the state, in my case NC. (The addressfield states are two letter abbreviations.)
Save.
This will take you back to the previous screen. Now click on +Add or, choose continue on the next screen. Now under "Conditions" there is the first condition "Order address component comparison" and the next line says "OR". Under the operations column by the OR choose Add condition.
On the next screen, in the drop down for "Select the condition to add", choose Order address component comparison.
The next screen has five sections; Order, Address, Address component, Operator and Value. (We have been here before.)
Under Order, choose commerce-line-item:order.
Under Address, choose Billing information address.
Under Address component, choose Sub-administrative area (District). This list is built from the edited addressfield example module mentioned earlier.
Under operator, choose equals
Under Value, fill in the county, in my case the first one is Alamance.
Save.
Now you could keep adding conditions under the OR section in this manner, but at this point, depending on how many counties this condition applies to, it would be easier to go to admin/config/workflow/rules/components, find the component and to the far right click on export. This will show a screen where you can copy the rule component and then paste it in a text editor. It will look something like this.
{ "commerce_tax_rate_nc_county_sales_tax" : {"LABEL" : "Calculate NC county sales tax",
"PLUGIN" : "rule",
"TAGS" : [ "Commerce Tax", "sales_tax" ],
"REQUIRES" : [ "commerce_order", "commerce_tax" ],
"USES VARIABLES" : { "commerce_line_item" : { "label" : "Line item", "type" : "commerce_line_item" } },
"IF" : [
{ "commerce_order_compare_address" : {
"commerce_order" : [ "commerce-line-item:order" ],
"address_field" : "commerce_customer_billing|commerce_customer_address",
"address_component" : "administrative_area",
"value" : "NC"
}
},
{ "OR" : [
{ "commerce_order_compare_address" : {
"commerce_order" : [ "commerce-line-item:order" ],
"address_field" : "commerce_customer_billing|commerce_customer_address",
"address_component" : "sub_administrative_area",
"value" : "Alamance"
}
},
}
]
}
],
"DO" : [
{ "commerce_tax_rate_apply" : {
"USING" : {
"commerce_line_item" : [ "commerce-line-item" ],
"tax_rate_name" : "nc_county_sales_tax"
},
"PROVIDE" : { "applied_tax" : { "applied_tax" : "Applied tax" } }
}
}
]
}
}
In your text editor, under the OR section, copy the section that looks like this:
{ "commerce_order_compare_address" : {"commerce_order" : [ "commerce-line-item:order" ],
"address_field" : "commerce_customer_billing|commerce_customer_address",
"address_component" : "sub_administrative_area",
"value" : "Alamance"
}
},
and paste it, one right after the other, for all the counties that require that tax. On this line "value" : "Alamance", replace Alamance with the appropriate counties. The text will now look something like this:
{ "commerce_tax_rate_nc_county_sales_tax" : {"LABEL" : "Calculate NC county sales tax",
"PLUGIN" : "rule",
"TAGS" : [ "Commerce Tax", "sales_tax" ],
"REQUIRES" : [ "commerce_order", "commerce_tax" ],
"USES VARIABLES" : { "commerce_line_item" : { "label" : "Line item", "type" : "commerce_line_item" } },
"IF" : [
{ "commerce_order_compare_address" : {
"commerce_order" : [ "commerce-line-item:order" ],
"address_field" : "commerce_customer_billing|commerce_customer_address",
"address_component" : "administrative_area",
"value" : "NC"
}
},
{ "OR" : [
{ "commerce_order_compare_address" : {
"commerce_order" : [ "commerce-line-item:order" ],
"address_field" : "commerce_customer_billing|commerce_customer_address",
"address_component" : "sub_administrative_area",
"value" : "Alamance"
}
},
{ "commerce_order_compare_address" : {
"commerce_order" : [ "commerce-line-item:order" ],
"address_field" : "commerce_customer_billing|commerce_customer_address",
"address_component" : "sub_administrative_area",
"value" : "Alexander"
}
},
{ "commerce_order_compare_address" : {
"commerce_order" : [ "commerce-line-item:order" ],
"address_field" : "commerce_customer_billing|commerce_customer_address",
"address_component" : "sub_administrative_area",
"value" : "Alleghany"
}
},
}
]
}
],
"DO" : [
{ "commerce_tax_rate_apply" : {
"USING" : {
"commerce_line_item" : [ "commerce-line-item" ],
"tax_rate_name" : "nc_county_sales_tax"
},
"PROVIDE" : { "applied_tax" : { "applied_tax" : "Applied tax" } }
}
}
]
}
}
Now that we have all the counties in the rule, copy all the text.
Go back to admin/config/workflow/rules/components. At the top of the page choose Import component. A screen similar to the export screen appears. In the field, paste the copied text from the text editor. At the bottom, click the checkbox beside Overwrite, and save. Now repeat the entire process for each tax you create back at admin/commerce/config/taxes.
Addressfield Example
To create the addressfield counties module, copy the example module into a new folder and rename the folder, in my case - addressfield_nccounties. The new folder will have a directory like this:
addressfield_nccounties
-Plugins (folder)
--Format (folder)
---addressfield-nccounties.inc
addressfield_nccounties.info
addressfield_nccounties.module
Begin by editing the info file.
name = Address Field NC Counties
description = A module to add NC counties to addressfield for applying rules to add sales tax.
core = 7.x
package = Fields
; hidden = TRUE
dependencies[] = ctools
dependencies[] = addressfield
; Information added by drupal.org packaging script on 2012-05-29
version = "7.x-1.0-beta4"
core = "7.x"
project = "addressfield"
datestamp = "1367945112"
NB: When Addressfield module updates, change the version number in this file to the new version number
Then edit the Module file:
<?php
/**
* @file
* Defines a field for attaching North Carolina sub administrative areas (counties) addresses to entities.
*/
/**
* Implements hook_ctools_plugin_directory().
*/
function addressfield_nccounties_ctools_plugin_directory($module, $plugin) {
if ($module == 'addressfield') {
return 'plugins/' . $plugin;
}
}
?>
Now edit the plugins/format/addressfield-nccounties.inc
<?php
/**
* @file
* A specific handler for NC counties.
*/
$plugin = array(
'title' => t('Address sub_administrative_area'),
'format callback' => 'addressfield_format_sub_administrative_area_generate',
'type' => 'sub_administrative_area',
'weight' => -80,
);
/**
* Format callback.
*
* @see CALLBACK_addressfield_format_callback()
*/
function addressfield_format_sub_administrative_area_generate(&$format, $address) {
$format['sub_administrative_area_block'] = array(
'#type' => 'addressfield_container',
'#attributes' => array('class' => array('addressfield-container-inline', 'name-block')),
'#weight' => 40,
);
$format['locality_block']['sub_administrative_area'] = array(
'#title' => t('NC residence choose a county'),
'#size' => 30,
'#attributes' => array('class' => array('sub_administrative_area')),
'#required' => TRUE,
'#prefix' => ' ',
);
$format['locality_block']['sub_administrative_area']['#options'] = array(
/**'' => t('--'),*/
'none' => t('not applicable'),
'Alamance' => t('Alamance'),
'Alexander' => t('Alexander'),
'Alleghany' => t('Alleghany'),
'Anson' => t('Anson'),
'Ashe' => t('Ashe'),
'Avery' => t('Avery'),
'Beaufort' => t('Beaufort'),
'Bertie' => t('Bertie'),
'Bladen' => t('Bladen'),
'Brunswick' => t('Brunswick'),
'Buncombe' => t('Buncombe'),
'Burke' => t('Burke'),
'Cabarrus' => t('Cabarrus'),
'Caldwell' => t('Caldwell'),
'Camden' => t('Camden'),
'Carteret' => t('Carteret'),
'Caswell' => t('Caswell'),
'Catawba' => t('Catawba'),
'Chatham' => t('Chatham'),
'Cherokee' => t('Cherokee'),
'Chowan' => t('Chowan'),
'Clay' => t('Clay'),
'Cleveland' => t('Cleveland'),
'Columbus' => t('Columbus'),
'Craven' => t('Craven'),
'Cumberland' => t('Cumberland'),
'Currituck' => t('Currituck'),
'Dare' => t('Dare'),
'Davidson' => t('Davidson'),
'Davie' => t('Davie'),
'Duplin' => t('Duplin'),
'Durham' => t('Durham'),
'Edgecombe' => t('Edgecombe'),
'Forsyth' => t('Forsyth'),
'Franklin' => t('Franklin'),
'Gaston' => t('Gaston'),
'Gates' => t('Gates'),
'Graham' => t('Graham'),
'Granville' => t('Granville'),
'Greene' => t('Greene'),
'Guilford' => t('Guilford'),
'Halifax' => t('Halifax'),
'Harnett' => t('Harnett'),
'Haywood' => t('Haywood'),
'Henderson' => t('Henderson'),
'Hertford' => t('Hertford'),
'Hoke' => t('Hoke'),
'Hyde' => t('Hyde'),
'Iredell' => t('Iredell'),
'Jackson' => t('Jackson'),
'Johnston' => t('Johnston'),
'Jones' => t('Jones'),
'Lee' => t('Lee'),
'Lenoir' => t('Lenoir'),
'Lincoln' => t('Lincoln'),
'Macon' => t('Macon'),
'Madison' => t('Madison'),
'Martin' => t('Martin'),
'McDowell' => t('McDowell'),
'Mecklenburg' => t('Mecklenburg'),
'Mitchell' => t('Mitchell'),
'Montgomery' => t('Montgomery'),
'Moore' => t('Moore'),
'Nash' => t('Nash'),
'New Hanover' => t('New Hanover'),
'Northampton' => t('Northampton'),
'Onslow' => t('Onslow'),
'Orange' => t('Orange'),
'Pamlico' => t('Pamlico'),
'Pasquotank' => t('Pasquotank'),
'Pender' => t('Pender'),
'Perquimans' => t('Perquimans'),
'Person' => t('Person'),
'Pitt' => t('Pitt'),
'Polk' => t('Polk'),
'Randolph' => t('Randolph'),
'Richmond' => t('Richmond'),
'Robeson' => t('Robeson'),
'Rockingham' => t('Rockingham'),
'Rowan' => t('Rowan'),
'Rutherford' => t('Rutherford'),
'Sampson' => t('Sampson'),
'Scotland' => t('Scotland'),
'Stanly' => t('Stanly'),
'Stokes' => t('Stokes'),
'Surry' => t('Surry'),
'Swain' => t('Swain'),
'Transylvania' => t('Transylvania'),
'Tyrrell' => t('Tyrrell'),
'Union' => t('Union'),
'Vance' => t('Vance'),
'Wake' => t('Wake'),
'Warren' => t('Warren'),
'Washington' => t('Washington'),
'Watauga' => t('Watauga'),
'Wayne' => t('Wayne'),
'Wilkes' => t('Wilkes'),
'Wilson' => t('Wilson'),
'Yadkin' => t('Yadkin'),
'Yancey' => t('Yancey'),
);
}
?>
To get that to work in the address field...
Navigate to admin/commerce/customer-profiles/types/billing/display. In the Format column, expand the drop down field and select Address components. Then click on the gear widget at the right.
When the gear widget is activated, the screen will change. On this screen, under Components to render select the desired components making sure you choose Sub administrative area. Then click Save
Navigate to admin/commerce/customer-profiles/types/billing/fields (or choose the "Manage Fields" tab). In the Operations column, click on the edit link beside "Dynamic address form".
The screen will change. On this form, make it a required field. In the Format Handlers section, choose Sub administrative area, and other desired values. The DEFAULT VALUE displays the current address field form. Save the form. The screen will go back to the previous one. Click on "edit" again to see the changes.
Wait wait
Wait wait there is one more trick to make NC residents choose a county on the submission form.
Go to admin/commerce/config/checkout/rules and click add a rule. On the next screen fill in the name and taxanomy terms and save. The next screen has four sections: Events, Conditions, Actions and Settings.
I added two events:
Completing the checkout process
After updating an existing commerce order
I added two conditions:
Order address component comparison
Parameter: Order: [commerce_order], Address: Address, Address component: Administrative area (State ..., Value: NC
Order address component comparison
Parameter: Order: [commerce_order], Address: Address, Address component: Sub-administrative area ..., Value: none
Undre Actions I added "show a message" and set an action:
Show a message on the site
Parameter: Message: NC residents must choose a..., Message type: Error, Repeat message: false
action set: Set order status : Checkout (checkout)
Parameter: Order: [commerce-order]
This rule prevents the customer from advancing in the order process, if they chose NC and did not choose a county.
subject to change
The tax rates in NC have changed. So keeping up to date will require the same procedures as above, but checking http://www.dornc.com/taxes/sales/salesrates_4-13.html for current rates.