+
+ {% endblock %}
diff --git a/templates/form/bootstrap_4.html.twig b/templates/form/bootstrap_4.html.twig
new file mode 100644
index 0000000..b5b248b
--- /dev/null
+++ b/templates/form/bootstrap_4.html.twig
@@ -0,0 +1,64 @@
+{% use 'bootstrap_4_layout.html.twig' %}
+
+{# copied from vendor/easycorp/easyadmin-bundle/src/Resources/views/form/bootstrap_4.html.twig #}
+{% block form_row -%}
+ {% set _field_type = easyadmin.field.fieldType|default('default') %}
+
+{%- endblock form_row %}
+
+{# copied from vendor/easycorp/easyadmin-bundle/src/Resources/views/form/bootstrap_4.html.twig #}
+{% block file_widget -%}
+ {% if vich|default(false) %}
+ {%- set type = type|default('file') -%}
+ {{- block('form_widget_simple') -}}
+ {% else %}
+ {{- parent() -}}
+
+
+ {% endif %}
+{%- endblock %}
diff --git a/templates/helper/alerts.html.twig b/templates/helper/alerts.html.twig
new file mode 100644
index 0000000..9cf6928
--- /dev/null
+++ b/templates/helper/alerts.html.twig
@@ -0,0 +1,29 @@
+{% if has_alerts %}
+
+ {% if no_companies %}
+
+
You don't have companies!
+ Go to
companies
+ and create one. Then you will be able to download the QWC file!
+
+ {% endif %}
+
+ {% for company in accountless_companies %}
+
+
{{ company.companyName|default(company.qbUsername) }} has no accounts.
+ Synchronize chart of accounts
here
+ and you will be able to import transactions!
+
+ {% endfor %}
+
+
+ {% for type, messages in app.session.flashBag.all %}
+ {% for message in messages %}
+ {%if type == 'error'%} {% set type = 'danger' %} {%endif%}
+
+ {{ message|raw }}
+
+ {% endfor %}
+ {% endfor %}
+
+{% endif %}
diff --git a/templates/helper/render_preview.html.twig b/templates/helper/render_preview.html.twig
new file mode 100644
index 0000000..dc7c341
--- /dev/null
+++ b/templates/helper/render_preview.html.twig
@@ -0,0 +1,3 @@
+
+
{{ entities|yaml_encode(3) }}
+
diff --git a/templates/import/create.html.twig b/templates/import/create.html.twig
new file mode 100644
index 0000000..c316ec3
--- /dev/null
+++ b/templates/import/create.html.twig
@@ -0,0 +1,102 @@
+{% extends 'admin.html.twig' %}
+
+{% block content_title %}Create import{% endblock %}
+
+{% block head_custom_stylesheets %}
+ {{ parent() }}
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
+ {{ form_start(form) }}
+ {{ form_errors(form) }}
+
+ {% for error in errors %}
+ {{ error }}
+ {% endfor %}
+
+ {% if flow.getCurrentStepLabel() == 'import_wizard_step.confirmation' %}
+
+ Preview
+
+ {{ render_preview(entities)|raw }}
+ {% endif %}
+
+ {{ form_rest(form) }}
+
+
+ {% include '@CraueFormFlow/FormFlow/buttons.html.twig' with {
+ craue_formflow_button_label_next: 'Next',
+ craue_formflow_button_label_back: 'Back',
+ craue_formflow_button_label_finish: 'Schedule import',
+ craue_formflow_button_class_last: 'btn btn-primary',
+ craue_formflow_button_class_back: 'btn',
+ craue_formflow_button_render_reset: 0,
+ } %}
+
+ {{ form_end(form) }}
+
+
+{% endblock %}
+
+
+{% block body_custom_javascript %}
+ {{ parent() }}
+
+{% endblock %}
diff --git a/templates/import/scheduled.html.twig b/templates/import/scheduled.html.twig
new file mode 100644
index 0000000..dbd2055
--- /dev/null
+++ b/templates/import/scheduled.html.twig
@@ -0,0 +1,9 @@
+{% extends 'admin.html.twig' %}
+
+{% block content_title %}Import scheduled{% endblock %}
+
+{% block content %}
+
+ Now go to quickbooks and run WebConnector
+
+{% endblock %}
diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig
new file mode 100644
index 0000000..ff8c111
--- /dev/null
+++ b/templates/registration/register.html.twig
@@ -0,0 +1,33 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}Register{% endblock %}
+
+{% block body %}
+
+
+
+
+ {% for flashError in app.flashes('verify_email_error') %}
+
{{ flashError }}
+ {% endfor %}
+
+
Register
+
+ {{ form_start(registrationForm) }}
+ {{ form_row(registrationForm.email) }}
+ {{ form_row(registrationForm.plainPassword, {
+ label: 'Password'
+ }) }}
+
+
Register
+
+
+
+ {{ form_end(registrationForm) }}
+
+
+
+
+{% endblock %}
diff --git a/templates/reset_password/check_email.html.twig b/templates/reset_password/check_email.html.twig
new file mode 100644
index 0000000..35f1153
--- /dev/null
+++ b/templates/reset_password/check_email.html.twig
@@ -0,0 +1,8 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}Password Reset Email Sent{% endblock %}
+
+{% block body %}
+
An email has been sent that contains a link that you can click to reset your password. This link will expire in {{ tokenLifetime|date('g') }} hour(s).
+
If you don't receive an email please check your spam folder or try again .
+{% endblock %}
diff --git a/templates/reset_password/email.html.twig b/templates/reset_password/email.html.twig
new file mode 100644
index 0000000..d02ca97
--- /dev/null
+++ b/templates/reset_password/email.html.twig
@@ -0,0 +1,11 @@
+
Hi!
+
+
+ To reset your password, please visit
+ here
+ This link will expire in {{ tokenLifetime|date('g') }} hour(s)..
+
+
+
+ Cheers!
+
\ No newline at end of file
diff --git a/templates/reset_password/request.html.twig b/templates/reset_password/request.html.twig
new file mode 100644
index 0000000..b5dff14
--- /dev/null
+++ b/templates/reset_password/request.html.twig
@@ -0,0 +1,30 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}Reset your password{% endblock %}
+
+{% block body %}
+
+
+
+
+ {% for flashError in app.flashes('reset_password_error') %}
+
{{ flashError }}
+ {% endfor %}
+
Reset your password
+
+ {{ form_start(requestForm) }}
+ {{ form_row(requestForm.email) }}
+
+
+ Enter your email address and we we will send you a
+ link to reset your password.
+
+
+
+
Send password reset email
+ {{ form_end(requestForm) }}
+
+
+
+
+{% endblock %}
diff --git a/templates/reset_password/reset.html.twig b/templates/reset_password/reset.html.twig
new file mode 100644
index 0000000..8ffa72b
--- /dev/null
+++ b/templates/reset_password/reset.html.twig
@@ -0,0 +1,20 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}Reset your password{% endblock %}
+
+{% block body %}
+
+
+
+
+
Reset your password
+
+ {{ form_start(resetForm) }}
+ {{ form_row(resetForm.plainPassword) }}
+ Reset password
+ {{ form_end(resetForm) }}
+
+
+
+
+{% endblock %}
diff --git a/templates/scheduling/accounts.html.twig b/templates/scheduling/accounts.html.twig
new file mode 100644
index 0000000..e66978a
--- /dev/null
+++ b/templates/scheduling/accounts.html.twig
@@ -0,0 +1,28 @@
+{% extends 'admin.html.twig' %}
+
+{% form_theme schedule_accounts_update_form with easyadmin_config('design.form_theme') %}
+
+{% block content_title %}Scheduling{% endblock %}
+
+{% block content %}
+
+
+
+
+
+ {{ form(schedule_accounts_update_form) }}
+
+
+
+
+
+
+{% endblock %}
diff --git a/templates/scheduling/schedule.html.twig b/templates/scheduling/schedule.html.twig
new file mode 100644
index 0000000..e0ad5e1
--- /dev/null
+++ b/templates/scheduling/schedule.html.twig
@@ -0,0 +1,76 @@
+{% extends 'admin.html.twig' %}
+
+{% form_theme schedule_form with easyadmin_config('design.form_theme') %}
+{% form_theme sample_form with easyadmin_config('design.form_theme') %}
+{% form_theme truncate_form with easyadmin_config('design.form_theme') %}
+{% form_theme converter_form with easyadmin_config('design.form_theme') %}
+
+{% block content_title %}Scheduling{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ {{ form(schedule_form) }}
+
+
+
+
+
+
+ {{ form(sample_form) }}
+
+
+
+
+
+
+ {{ form(converter_form) }}
+
+
+
+
+
+
+ {{ form(truncate_form) }}
+
+
+
+
+
+{% endblock %}
diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig
new file mode 100644
index 0000000..f8614bd
--- /dev/null
+++ b/templates/security/login.html.twig
@@ -0,0 +1,58 @@
+{% extends 'base.html.twig' %}
+
+{% block title %}Log in!{% endblock %}
+
+{% block body %}
+
+
+
+{% endblock %}
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/tests/Functional/FakeCurrencyExchanger.php b/tests/Functional/FakeCurrencyExchanger.php
new file mode 100644
index 0000000..7b5f6a9
--- /dev/null
+++ b/tests/Functional/FakeCurrencyExchanger.php
@@ -0,0 +1,13 @@
+client = self::createClient();
+
+ /** @var UserService $userService */
+ $userService = static::$container->get(UserService::class);
+ $this->user = $userService->createUser('admin@localhost', 'pass', ['ROLE_ADMIN']);
+
+ ob_flush();
+ }
+ public function tearDown(): void
+ {
+ ob_flush();
+ }
+
+ public function testExchange(): void
+ {
+ /** @var QuickbooksServer $server */
+ $server = static::$container->get(QuickbooksServer::class);
+ $server->truncateQueue();
+ $this->givenCompany($this->user, self::USERNAME, self::PASSWORD);
+
+ $customerAddXml = $this->getFixture('add_customer.xml');
+ $server->schedule(self::USERNAME,QUICKBOOKS_ADD_CUSTOMER, '100', $customerAddXml);
+
+
+ #authentication
+ $request = $this->getFixture('authenticate_request.xml');
+ $this->patchServer('POST', '/qbwc');
+ $this->client->request('POST', '/qbwc', [], [], [], $request);
+
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+ self::assertSame(1, preg_match('|
(.+) |', $response->getContent(), $matches));
+ $ticketId = $matches[1];
+
+ $expectedResponse = $this->getFixture('authenticate_response.xml', ['{ticketId}' => $ticketId]);
+ self::assertSame($expectedResponse, $response->getContent());
+
+ #task request
+ $request = $this->getFixture('task_request.xml', ['{ticketId}' => $ticketId]);
+ $this->patchServer('POST', '/qbwc');
+ $this->client->request('POST', '/qbwc', [], [], [], $request);
+
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+
+ $expectedResponse = $this->getFixture('task_add_customer_response.xml');
+ self::assertSame($expectedResponse, $response->getContent());
+
+ #task request added
+ $request = $this->getFixture('task_add_customer_added_request.xml', ['{ticketId}' => $ticketId]);
+ $this->patchServer('POST', '/qbwc');
+ $this->client->request('POST', '/qbwc', [], [], [], $request);
+
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+
+ $expectedResponse = $this->getFixture('task_add_customer_added_received_response.xml');
+ self::assertSame($expectedResponse, $response->getContent());
+
+ #bye
+ $request = $this->getFixture('bye_request.xml', ['{ticketId}' => $ticketId]);
+ $this->patchServer('POST', '/qbwc');
+ $this->client->request('POST', '/qbwc', [], [], [], $request);
+
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+
+ $expectedResponse = $this->getFixture('bye_response.xml');
+ self::assertSame($expectedResponse, $response->getContent());
+ }
+
+ public function testSchedule(): void
+ {
+ self::bootKernel();
+
+ /** @var QuickbooksServer $server */
+ $server = static::$container->get(QuickbooksServer::class);
+ $server->truncateQueue();
+
+ /** @var EntityManagerInterface $em */
+ $em = static::$container->get(EntityManagerInterface::class);
+ $repo = $em->getRepository(QuickbooksQueue::class);
+ self::assertEmpty($repo->findAll());
+
+ #WHEN
+ $server->schedule(self::USERNAME,QUICKBOOKS_ADD_CUSTOMER, '100', '
', ['a' => 'b']);
+
+ #THEN
+ $items = $repo->findAll();
+ self::assertCount(1, $items);
+ [$item] = $items;
+
+ self::assertSame(QUICKBOOKS_ADD_CUSTOMER, $item->getQbAction());
+ self::assertSame('100', $item->getIdent());
+ self::assertSame('
', $item->getQbxml());
+ self::assertSame(['a' => 'b'], $item->getExtraData());
+ }
+
+ public function testDownloadQBWCConfig(): void
+ {
+ $this->givenCompany($this->user, self::USERNAME, self::PASSWORD);
+ $this->logIn($this->user);
+
+ $this->client->request('GET', '/download-qbwc-config?id='.self::USERNAME);
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+ self::assertStringStartsWith('', $response->getContent());
+ self::assertSame('text/xml; charset=UTF-8', $response->headers->get('content-type'));
+ self::assertStringStartsWith('attachment; filename=', $response->headers->get('content-disposition'));
+ }
+
+ private function givenCompany(User $user, string $username, ?string $password): QuickbooksCompany
+ {
+ /** @var EntityManagerInterface $em */
+ $em = static::$container->get(EntityManagerInterface::class);
+ $c = $em->getRepository(QuickbooksCompany::class)->findOneBy(['qbUsername' => $username]);
+ if (null === $c) {
+ $c = new QuickbooksCompany($username);
+ $em->persist($c);
+ }
+ $c->setCompanyName($username);
+ $c->setQbPassword($password);
+ $c->setUser($user);
+ $em->flush();
+
+ return $c;
+ }
+
+ private function logIn(User $user)
+ {
+ $session = self::$container->get('session');
+
+ $firewallName = 'main';
+ // if you don't define multiple connected firewalls, the context defaults to the firewall name
+ // See https://symfony.com/doc/current/reference/configuration/security.html#firewall-context
+ $firewallContext = 'main';
+
+ // you may need to use a different token class depending on your application.
+ // for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken
+ $token = new UsernamePasswordToken($user, null, $firewallName, $user->getRoles());
+ $session->set('_security_'.$firewallContext, serialize($token));
+ $session->save();
+
+ $cookie = new Cookie($session->getName(), $session->getId());
+ $this->client->getCookieJar()->set($cookie);
+ }
+
+ public function testInfo(): void
+ {
+ $this->patchServer('GET', '/qbwc');
+ $this->client->request('GET', '/qbwc');
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+ self::assertSame('Easy Quick Import', $response->getContent());
+ self::assertSame('text/plain; charset=UTF-8', $response->headers->get('content-type'));
+ }
+
+ public function xtestWSDL(): void //exits so not testable
+ {
+ $this->patchServer('GET', '/qbwc?wsdl');
+ $this->client->request('GET', '/qbwc?wsdl');
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+ self::assertStringStartsWith('', $response->getContent());
+ self::assertSame('text/xml; charset=UTF-8', $response->headers->get('content-type'));
+ }
+
+ public function testNoDataExchangeRequired(): void
+ {
+ #server version exchange
+ $request = $this->getFixture('server_version_request.xml');
+ $this->patchServer('POST', '/qbwc');
+ $this->client->request('POST', '/qbwc', [], [], [], $request);
+
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+ self::assertSame($this->getFixture('server_version_response.xml'), $response->getContent());
+
+ #client version exchange
+ $request = $this->getFixture('client_version_request.xml');
+ $this->patchServer('POST', '/qbwc');
+ $this->client->request('POST', '/qbwc', [], [], [], $request);
+
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+ self::assertSame($this->getFixture('client_version_response.xml'), $response->getContent());
+
+ #authentication
+ $request = $this->getFixture('authenticate_request.xml');
+ $this->patchServer('POST', '/qbwc');
+ $this->client->request('POST', '/qbwc', [], [], [], $request);
+
+ $response = $this->client->getResponse();
+ self::assertSame(200, $response->getStatusCode());
+ self::assertNotFalse(strpos($response->getContent(), '
'), $response->getContent());
+ }
+
+ private function getFixture(string $name, ?array $replacements = null): string
+ {
+ $content = file_get_contents(__DIR__ . '/fixtures/' . $name);
+
+ if ($replacements !== null) {
+ foreach ($replacements as $search => $replacement) {
+ $content = str_replace($search, $replacement, $content);
+ }
+ }
+ return $content;
+ }
+
+ private function patchServer(string $method, string $uri): void
+ {
+ $_SERVER['REQUEST_METHOD'] = $method;
+ $_SERVER['REQUEST_URI'] = $uri;
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $query = parse_url($uri, PHP_URL_QUERY);
+ if (null !== $query) {
+ parse_str($query, $_GET);
+ }
+ }
+}
diff --git a/tests/Functional/SchedulerTest.php b/tests/Functional/SchedulerTest.php
new file mode 100644
index 0000000..67315b9
--- /dev/null
+++ b/tests/Functional/SchedulerTest.php
@@ -0,0 +1,150 @@
+get(EntityManagerInterface::class);
+ if (null === $company = $em->find(QuickbooksCompany::class, self::TEST_USER)) {
+ $company = new QuickbooksCompany(self::TEST_USER);
+ $em->persist($company);
+ }
+ $company->setMultiCurrencyEnabled(true);
+ $em->flush();
+ $this->company = $company;
+
+ $accountRepo = static::$container->get(QuickbooksAccountRepository::class);
+ $accountRepo->deleteAll($this->company);
+
+ /** @var UserService $userService */
+ $userService = static::$container->get(UserService::class);
+ $this->user = $userService->createUser('admin@localhost', 'pass', ['ROLE_ADMIN']);
+
+ $this->scheduler = static::$container->get(SheetScheduler::class);
+ }
+
+ public function provider(): array
+ {
+ return [
+ ['vendor.csv', SheetScheduler::TYPE_VENDOR, 'vendor_converted.xml'],
+ ['bill.csv', SheetScheduler::TYPE_VENDOR_BILL, 'bill_converted.xml'],
+ ['customer.csv', SheetScheduler::TYPE_CUSTOMER, 'customer_converted.xml'],
+ ['invoice.csv', SheetScheduler::TYPE_CUSTOMER_INVOICE, 'invoice_converted.xml'],
+ ['transaction.csv', SheetScheduler::TYPE_TRANSACTION, 'transaction_converted.xml'],
+ ];
+ }
+
+ /**
+ * @dataProvider provider
+ */
+ public function testExchange(string $inputFile, string $type, string $expectedOuputFile): void
+ {
+ /** @var SheetScheduler $scheduler */
+ $scheduler = static::$container->get(SheetScheduler::class);
+ $expected = file_get_contents(__DIR__.'/fixtures/'.$expectedOuputFile);
+ $actual = $scheduler->dryRun($this->company, $type, $scheduler->copyToLocal($inputFile));
+ self::assertSame($expected, $actual);
+ }
+
+ public function testAccountRepo(): void
+ {
+ /** @var EntityManagerInterface $em */
+ $em = static::$container->get(EntityManagerInterface::class);
+ $accountRepo = static::$container->get(QuickbooksAccountRepository::class);
+
+ $account = new QuickbooksAccount();
+ $account->setCompany($this->company);
+ $account->setFullName('Bank USD');
+ $account->setAccountType(QuickbooksAccount::TYPE_BANK);
+ $account->setCurrency('US Dollar');
+ $account->setUser($this->user);
+ $em->persist($account);
+ $em->flush();
+
+ $currency = $accountRepo->getCurrency(self::TEST_USER, 'Bank USD', QuickbooksAccount::TYPE_BANK);
+ self::assertSame('US Dollar', $currency);
+
+ $currency = $accountRepo->getCurrency(self::TEST_USER, 'Bank USD');
+ self::assertSame('US Dollar', $currency);
+
+ $currency = $accountRepo->getCurrency(self::TEST_USER, 'Bank', QuickbooksAccount::TYPE_BANK);
+ self::assertNull($currency);
+
+ $currency = $accountRepo->getCurrency('', 'Bank USD', QuickbooksAccount::TYPE_BANK);
+ self::assertNull($currency);
+
+ $currency = $accountRepo->getCurrency(self::TEST_USER, 'Bank USD', QuickbooksAccount::TYPE_EXPENSE);
+ self::assertNull($currency);
+ }
+
+ public function testIdent()
+ {
+ /** @var SheetScheduler $scheduler */
+ $scheduler = static::$container->get(SheetScheduler::class);
+
+ self::assertSame('long-transactions-2856:1:JournalEntryAdd',
+ $scheduler->shrinkIdent('long-transactions-20200401-20200419.csv', '1', 'JournalEntryAdd'));
+
+ self::assertSame('long-invoices-20200bdc:2:JournalEntryAdd',
+ $scheduler->shrinkIdent('long-invoices-20200401-20200419.csv', '2', 'JournalEntryAdd'));
+
+ self::assertSame('long-transactions-856:11:JournalEntryAdd',
+ $scheduler->shrinkIdent('long-transactions-20200401-20200419.csv', '11', 'JournalEntryAdd'));
+
+ self::assertSame('lon856:11:JournalEntryAddJournalEntryAdd',
+ $scheduler->shrinkIdent('long-transactions-20200401-20200419.csv', '11', 'JournalEntryAddJournalEntryAdd'));
+ }
+
+ public function testIdentException()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Expected a value greater than 0. Got: -9');
+
+ /** @var SheetScheduler $scheduler */
+ $scheduler = static::$container->get(SheetScheduler::class);
+
+ self::assertSame('lon856:11:JournalEntryAddJournalEntryAddJournalEntryAdd',
+ $scheduler->shrinkIdent('long-transactions-20200401-20200419.csv', '11', 'JournalEntryAddJournalEntryAddJournalEntryAdd'));
+ }
+
+ public function testDateFormat()
+ {
+ $inv = new CustomerInvoice();
+ $inv->setTxnDate('Apr 15, 2020');
+ $this->scheduler->canonizeDate([$inv], 'M j, Y');
+ self::assertSame('2020-04-15', $inv->getTxnDate());
+ }
+
+ public function testDateFormatIncorrect()
+ {
+ $inv = new CustomerInvoice();
+ $inv->setTxnDate('AAA 15, 2020');
+ $this->scheduler->canonizeDate([$inv], 'M j, Y');
+ self::assertSame('AAA 15, 2020', $inv->getTxnDate());
+ }
+}
diff --git a/tests/Functional/fixtures/add_customer.xml b/tests/Functional/fixtures/add_customer.xml
new file mode 100644
index 0000000..0da67bb
--- /dev/null
+++ b/tests/Functional/fixtures/add_customer.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ Acme Inc.
+ Acme Inc.
+ John
+ Tulchin
+
+ 56 Cowles Road
+ Willington
+ CT
+ 06279
+ United States
+
+
+ US Dollar
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/authenticate_request.xml b/tests/Functional/fixtures/authenticate_request.xml
new file mode 100644
index 0000000..54a2c08
--- /dev/null
+++ b/tests/Functional/fixtures/authenticate_request.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ quickbooks
+ FE7XjHVzjfdH
+
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/authenticate_response.xml b/tests/Functional/fixtures/authenticate_response.xml
new file mode 100644
index 0000000..281f79d
--- /dev/null
+++ b/tests/Functional/fixtures/authenticate_response.xml
@@ -0,0 +1,8 @@
+
+
+ {ticketId}
+
+
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/bill.csv b/tests/Functional/fixtures/bill.csv
new file mode 100644
index 0000000..87e1d8c
--- /dev/null
+++ b/tests/Functional/fixtures/bill.csv
@@ -0,0 +1,2 @@
+memo,apAccount,refNumber,txnDate,line1AccountFullName,line1Amount,line1Memo,exchangeRate,vendorFullname,vendorCompanyName,addr1,addr2,vendorType,terms,currency,city,state,postalcode,country
+"Bill Memo","Accounts Payable",2018-05-10-0001,2018-05-10,"Rent Expense",10.00,"Item Memo",1,Silo,"Silo LIMITED","68 Tap Kwok Nam Path","Lok Sheuk Tsan","Service Providers","Due on receipt","Hong Kong Dollar","Hong Kong",HK,999077,"Hong Kong"
diff --git a/tests/Functional/fixtures/bill_converted.xml b/tests/Functional/fixtures/bill_converted.xml
new file mode 100644
index 0000000..a142049
--- /dev/null
+++ b/tests/Functional/fixtures/bill_converted.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+ Silo
+ Silo LIMITED
+
+ 68 Tap Kwok Nam Path
+ Lok Sheuk Tsan
+ Hong Kong
+ HK
+ 999077
+ Hong Kong
+
+
+ Service Providers
+
+
+ Due on receipt
+
+
+ Hong Kong Dollar
+
+
+
+
+
+
+ Silo
+
+
+ Accounts Payable
+
+ 2018-05-10
+ 2018-05-10-0001
+
+ Due on receipt
+
+ Bill Memo
+ 1
+
+
+ Rent Expense
+
+ 10.00
+ Item Memo
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/bye_request.xml b/tests/Functional/fixtures/bye_request.xml
new file mode 100644
index 0000000..b8fda72
--- /dev/null
+++ b/tests/Functional/fixtures/bye_request.xml
@@ -0,0 +1 @@
+{ticketId}
\ No newline at end of file
diff --git a/tests/Functional/fixtures/bye_response.xml b/tests/Functional/fixtures/bye_response.xml
new file mode 100644
index 0000000..fd09786
--- /dev/null
+++ b/tests/Functional/fixtures/bye_response.xml
@@ -0,0 +1,6 @@
+
+
+ Complete!
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/client_version_request.xml b/tests/Functional/fixtures/client_version_request.xml
new file mode 100644
index 0000000..f32d696
--- /dev/null
+++ b/tests/Functional/fixtures/client_version_request.xml
@@ -0,0 +1 @@
+2.3.0.25
\ No newline at end of file
diff --git a/tests/Functional/fixtures/client_version_response.xml b/tests/Functional/fixtures/client_version_response.xml
new file mode 100644
index 0000000..602a8db
--- /dev/null
+++ b/tests/Functional/fixtures/client_version_response.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/company_query_rs.xml b/tests/Functional/fixtures/company_query_rs.xml
new file mode 100644
index 0000000..71e8f61
--- /dev/null
+++ b/tests/Functional/fixtures/company_query_rs.xml
@@ -0,0 +1,202 @@
+
+
+
+
+
+ QuickBooks Desktop Pro 2019
+ 29
+ 0
+ US
+ 1.0
+ 1.1
+ 2.0
+ 2.1
+ 3.0
+ 4.0
+ 4.1
+ 5.0
+ 6.0
+ 7.0
+ 8.0
+ 9.0
+ 10.0
+ 11.0
+ 12.0
+ 13.0
+ false
+ SingleUser
+
+
+
+
+ false
+ Acme Inc
+ Acme Inc
+ January
+ January
+ ProfessionalConsulting
+ Form1040
+
+
+ QuickBooks Online Banking
+ banking.qb
+ Never
+
+
+ QuickBooks Online Billing
+ billing.qb
+ Never
+
+
+ QuickBooks Online Billing Level 1 Service
+ qbob1.qbn
+ Never
+
+
+ QuickBooks Online Billing Level 2 Service
+ qbob2.qbn
+ Never
+
+
+ QuickBooks Online Billing Payment Service
+ qbobpay.qbn
+ Never
+
+
+ QuickBooks Bill Payment
+ billpay.qb
+ Never
+
+
+ QuickBooks Online Billing Paper Mailing Service
+ qbobpaper.qbn
+ Never
+
+
+ QuickBooks Payroll Service
+ payroll.qb
+ Never
+
+
+ QuickBooks Basic Payroll Service
+ payrollbsc.qb
+ Never
+
+
+ QuickBooks Basic Disk Payroll Service
+ payrollbscdisk.qb
+ Never
+
+
+ QuickBooks Deluxe Payroll Service
+ payrolldlx.qb
+ Never
+
+
+ QuickBooks Premier Payroll Service
+ payrollprm.qb
+ Never
+
+
+ Basic Plus Federal
+ basic_plus_fed.qb
+ Never
+
+
+ Basic Plus Federal and State
+ basic_plus_fed_state.qb
+ Never
+
+
+ Basic Plus Direct Deposit
+ basic_plus_dd.qb
+ Never
+
+
+ Merchant Account Service
+ mas.qbn
+ Never
+
+
+
+ false
+
+
+ {0e102412-c367-403d-806f-28b2bea18297}
+ AppLock
+ STR255TYPE
+ LOCKED:WIN-OPFD9B31KJ1:637251322674318831
+
+
+ {0e102412-c367-403d-806f-28b2bea18297}
+ FileID
+ STR255TYPE
+ {0e102412-c367-403d-806f-28b2bea18297}
+
+
+
+
+
+
+ false
+ true
+ false
+ true
+ true
+
+
+ 0,0
+ 0,00
+ 0
+ false
+ DueDate
+ false
+
+
+ true
+ false
+ false
+
+
+ false
+
+
+ false
+ false
+
+
+ false
+ 10
+ false
+
+
+ AgeFromDueDate
+ Accrual
+
+
+ false
+ true
+
+ true
+ true
+
+
+
+ Monday
+
+
+ true
+ Admin
+ false
+
+
+ false
+ None
+ false
+ false
+ false
+
+
+
+
+
diff --git a/tests/Functional/fixtures/customer.csv b/tests/Functional/fixtures/customer.csv
new file mode 100644
index 0000000..7d2433a
--- /dev/null
+++ b/tests/Functional/fixtures/customer.csv
@@ -0,0 +1,2 @@
+customerFullName,firstName,lastName,companyName,terms,currency,addr1,addr2,city,state,postalcode,country
+HomeBase,Long,Allen,"HomeBase LLC","Due on receipt","US Dollar","4420 Shadowmar Drive",Louisiana,"New Orleans",LA,70112,"United States"
diff --git a/tests/Functional/fixtures/customer_converted.xml b/tests/Functional/fixtures/customer_converted.xml
new file mode 100644
index 0000000..922680d
--- /dev/null
+++ b/tests/Functional/fixtures/customer_converted.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ HomeBase
+ HomeBase LLC
+ Long
+ Allen
+
+ Attn: Long Allen
+ HomeBase LLC
+ 4420 Shadowmar Drive
+ Louisiana
+ New Orleans
+ LA
+ 70112
+ United States
+
+
+ Due on receipt
+
+
+ US Dollar
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/invoice.csv b/tests/Functional/fixtures/invoice.csv
new file mode 100644
index 0000000..a2c58f0
--- /dev/null
+++ b/tests/Functional/fixtures/invoice.csv
@@ -0,0 +1,2 @@
+refNumber,invoiceMemo,arAccount,txnDate,exchangeRate,line1ItemName,line1Desc,line1Quantity,line1Amount,line1Rate,customerFullName,firstName,lastName,companyName,terms,currency,addr1,addr2,city,state,postalcode,country
+180510-0001,"Invoice memo","Accounts Receivable - USD",2018-05-10,7.83126,Consulting,"Item desc",1,10.00,,HomeBase,Long,Allen,"HomeBase LLC","Due on receipt","US Dollar","4420 Shadowmar Drive",Louisiana,"New Orleans",LA,70112,"United States"
diff --git a/tests/Functional/fixtures/invoice_converted.xml b/tests/Functional/fixtures/invoice_converted.xml
new file mode 100644
index 0000000..6277d69
--- /dev/null
+++ b/tests/Functional/fixtures/invoice_converted.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ HomeBase
+ HomeBase LLC
+ Long
+ Allen
+
+ Attn: Long Allen
+ HomeBase LLC
+ 4420 Shadowmar Drive
+ Louisiana
+ New Orleans
+ LA
+ 70112
+ United States
+
+
+ Due on receipt
+
+
+ US Dollar
+
+
+
+
+
+
+ HomeBase
+
+
+ Accounts Receivable - USD
+
+ 2018-05-10
+ 180510-0001
+
+ Attn: Long Allen
+ HomeBase LLC
+ 4420 Shadowmar Drive
+ Louisiana
+ New Orleans
+ LA
+ 70112
+ United States
+
+
+ Due on receipt
+
+ Invoice memo
+ 7.83126
+
+
+ Consulting
+
+ Item desc
+ 1
+
+ 10.00
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/server_version_request.xml b/tests/Functional/fixtures/server_version_request.xml
new file mode 100644
index 0000000..2c9bce3
--- /dev/null
+++ b/tests/Functional/fixtures/server_version_request.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/server_version_response.xml b/tests/Functional/fixtures/server_version_response.xml
new file mode 100644
index 0000000..2c5a360
--- /dev/null
+++ b/tests/Functional/fixtures/server_version_response.xml
@@ -0,0 +1,6 @@
+
+
+ PHP QuickBooks SOAP Server v3.0 at /qbwc
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/task_add_customer_added_received_response.xml b/tests/Functional/fixtures/task_add_customer_added_received_response.xml
new file mode 100644
index 0000000..d425a28
--- /dev/null
+++ b/tests/Functional/fixtures/task_add_customer_added_received_response.xml
@@ -0,0 +1,6 @@
+
+
+ 100
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/task_add_customer_added_request.xml b/tests/Functional/fixtures/task_add_customer_added_request.xml
new file mode 100644
index 0000000..eea02c2
--- /dev/null
+++ b/tests/Functional/fixtures/task_add_customer_added_request.xml
@@ -0,0 +1,41 @@
+{ticketId} <?xml version="1.0" ?>
+<QBXML>
+<QBXMLMsgsRs>
+<CustomerAddRs requestID="1" statusCode="0" statusSeverity="Info" statusMessage="Status OK">
+<CustomerRet>
+<ListID>80000009-1560962812</ListID>
+<TimeCreated>2019-06-19T09:46:52-08:00</TimeCreated>
+<TimeModified>2019-06-19T09:46:52-08:00</TimeModified>
+<EditSequence>1560962812</EditSequence>
+<Name>Acme Inc.</Name>
+<FullName>Acme Inc.</FullName>
+<IsActive>true</IsActive>
+<Sublevel>0</Sublevel>
+<CompanyName>Acme Inc.</CompanyName>
+<FirstName>John</FirstName>
+<LastName>Tulchin</LastName>
+<BillAddress>
+<Addr1>56 Cowles Road</Addr1>
+<City>Willington</City>
+<State>CT</State>
+<PostalCode>06279</PostalCode>
+<Country>USA</Country>
+</BillAddress>
+<BillAddressBlock>
+<Addr1>56 Cowles Road</Addr1>
+<Addr2>Willington, CT 06279</Addr2>
+<Addr3>United States</Addr3>
+</BillAddressBlock>
+<Balance>0.00</Balance>
+<TotalBalance>0.00</TotalBalance>
+<JobStatus>None</JobStatus>
+<PreferredDeliveryMethod>None</PreferredDeliveryMethod>
+<CurrencyRef>
+<ListID>80000096-1560777869</ListID>
+<FullName>US Dollar</FullName>
+</CurrencyRef>
+</CustomerRet>
+</CustomerAddRs>
+</QBXMLMsgsRs>
+</QBXML>
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/task_add_customer_response.xml b/tests/Functional/fixtures/task_add_customer_response.xml
new file mode 100644
index 0000000..05348e8
--- /dev/null
+++ b/tests/Functional/fixtures/task_add_customer_response.xml
@@ -0,0 +1,30 @@
+
+
+ <?xml version="1.0" encoding="utf-8"?>
+ <?qbxml version="13.0"?>
+ <QBXML>
+ <QBXMLMsgsRq onError="continueOnError">
+ <CustomerAddRq requestID="1">
+ <CustomerAdd>
+ <Name>Acme Inc.</Name>
+ <CompanyName>Acme Inc.</CompanyName>
+ <FirstName>John</FirstName>
+ <LastName>Tulchin</LastName>
+ <BillAddress>
+ <Addr1>56 Cowles Road</Addr1>
+ <City>Willington</City>
+ <State>CT</State>
+ <PostalCode>06279</PostalCode>
+ <Country>United States</Country>
+ </BillAddress>
+ <CurrencyRef>
+ <FullName>US Dollar</FullName>
+ </CurrencyRef>
+ </CustomerAdd>
+</CustomerAddRq>
+
+ </QBXMLMsgsRq>
+ </QBXML>
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/task_request.xml b/tests/Functional/fixtures/task_request.xml
new file mode 100644
index 0000000..b9c9fd4
--- /dev/null
+++ b/tests/Functional/fixtures/task_request.xml
@@ -0,0 +1,226 @@
+{ticketId} <?xml version="1.0" ?>
+<QBXML>
+<QBXMLMsgsRs>
+<HostQueryRs requestID="0" statusCode="0" statusSeverity="Info" statusMessage="Status OK">
+<HostRet>
+<ProductName>QuickBooks Desktop Pro 2019</ProductName>
+<MajorVersion>29</MajorVersion>
+<MinorVersion>0</MinorVersion>
+<Country>US</Country>
+<SupportedQBXMLVersion>1.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>1.1</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>2.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>2.1</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>3.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>4.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>4.1</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>5.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>6.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>7.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>8.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>9.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>10.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>11.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>12.0</SupportedQBXMLVersion>
+<SupportedQBXMLVersion>13.0</SupportedQBXMLVersion>
+<IsAutomaticLogin>false</IsAutomaticLogin>
+<QBFileMode>SingleUser</QBFileMode>
+</HostRet>
+</HostQueryRs>
+<CompanyQueryRs requestID="1" statusCode="0" statusSeverity="Info" statusMessage="Status OK">
+<CompanyRet>
+<IsSampleCompany>false</IsSampleCompany>
+<CompanyName>ACME LIMITED</CompanyName>
+<LegalCompanyName>ACME LIMITED</LegalCompanyName>
+<Address>
+<Addr1>1104A, Kai Tak Comm Bld, </Addr1>
+<Addr2>317-319 Des Voeux Rd. Central</Addr2>
+<City>Hong Kong</City>
+<PostalCode>999077</PostalCode>
+<Country>Other</Country>
+</Address>
+<AddressBlock>
+<Addr1>1104A, Kai Tak Comm Bld,</Addr1>
+<Addr2>317-319 Des Voeux Rd. Central</Addr2>
+<Addr3>Hong Kong, 999077</Addr3>
+</AddressBlock>
+<LegalAddress>
+<Addr1>1104A, Kai Tak Comm Bld,</Addr1>
+<Addr2>317-319 Des Voeux Rd. Central</Addr2>
+<City>Hong Kong</City>
+<PostalCode>999077</PostalCode>
+<Country>US</Country>
+</LegalAddress>
+<Email>info@acme.com</Email>
+<FirstMonthFiscalYear>January</FirstMonthFiscalYear>
+<FirstMonthIncomeTaxYear>January</FirstMonthIncomeTaxYear>
+<CompanyType>InformationTechnologyComputersSoftware</CompanyType>
+<TaxForm>Form1065</TaxForm>
+<SubscribedServices>
+<Service>
+<Name>QuickBooks Online Banking</Name>
+<Domain>banking.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Online Billing</Name>
+<Domain>billing.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Online Billing Level 1 Service</Name>
+<Domain>qbob1.qbn</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Online Billing Level 2 Service</Name>
+<Domain>qbob2.qbn</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Online Billing Payment Service</Name>
+<Domain>qbobpay.qbn</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Bill Payment</Name>
+<Domain>billpay.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Online Billing Paper Mailing Service</Name>
+<Domain>qbobpaper.qbn</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Payroll Service</Name>
+<Domain>payroll.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Basic Payroll Service</Name>
+<Domain>payrollbsc.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Basic Disk Payroll Service</Name>
+<Domain>payrollbscdisk.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Deluxe Payroll Service</Name>
+<Domain>payrolldlx.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>QuickBooks Premier Payroll Service</Name>
+<Domain>payrollprm.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>Basic Plus Federal</Name>
+<Domain>basic_plus_fed.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>Basic Plus Federal and State</Name>
+<Domain>basic_plus_fed_state.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>Basic Plus Direct Deposit</Name>
+<Domain>basic_plus_dd.qb</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+<Service>
+<Name>Merchant Account Service</Name>
+<Domain>mas.qbn</Domain>
+<ServiceStatus>Never</ServiceStatus>
+</Service>
+</SubscribedServices>
+<AccountantCopy>
+<AccountantCopyExists>false</AccountantCopyExists>
+</AccountantCopy>
+<DataExtRet>
+<OwnerID>{0e77aa8a-0e9c-a864-41a7-4aa4f63858ab}</OwnerID>
+<DataExtName>AppLock</DataExtName>
+<DataExtType>STR255TYPE</DataExtType>
+<DataExtValue>LOCKED:WIN-KDMFU3DFTBK:636965595837848585</DataExtValue>
+</DataExtRet>
+<DataExtRet>
+<OwnerID>{0e77aa8a-0e9c-a864-41a7-4aa4f63858ab}</OwnerID>
+<DataExtName>FileID</DataExtName>
+<DataExtType>STR255TYPE</DataExtType>
+<DataExtValue>{7e113158-25d1-2004-4d47-a62929e377f2}</DataExtValue>
+</DataExtRet>
+</CompanyRet>
+</CompanyQueryRs>
+<PreferencesQueryRs requestID="2" statusCode="0" statusSeverity="Info" statusMessage="Status OK">
+<PreferencesRet>
+<AccountingPreferences>
+<IsUsingAccountNumbers>false</IsUsingAccountNumbers>
+<IsRequiringAccounts>true</IsRequiringAccounts>
+<IsUsingClassTracking>false</IsUsingClassTracking>
+<IsUsingAuditTrail>true</IsUsingAuditTrail>
+<IsAssigningJournalEntryNumbers>true</IsAssigningJournalEntryNumbers>
+</AccountingPreferences>
+<FinanceChargePreferences>
+<AnnualInterestRate>0.00</AnnualInterestRate>
+<MinFinanceCharge>0.00</MinFinanceCharge>
+<GracePeriod>0</GracePeriod>
+<IsAssessingForOverdueCharges>false</IsAssessingForOverdueCharges>
+<CalculateChargesFrom>DueDate</CalculateChargesFrom>
+<IsMarkedToBePrinted>false</IsMarkedToBePrinted>
+</FinanceChargePreferences>
+<JobsAndEstimatesPreferences>
+<IsUsingEstimates>true</IsUsingEstimates>
+<IsUsingProgressInvoicing>false</IsUsingProgressInvoicing>
+<IsPrintingItemsWithZeroAmounts>false</IsPrintingItemsWithZeroAmounts>
+</JobsAndEstimatesPreferences>
+<MultiCurrencyPreferences>
+<IsMultiCurrencyOn>true</IsMultiCurrencyOn>
+<HomeCurrencyRef>
+<ListID>8000003F-1560777868</ListID>
+<FullName>Hong Kong Dollar</FullName>
+</HomeCurrencyRef>
+</MultiCurrencyPreferences>
+<MultiLocationInventoryPreferences>
+<IsMultiLocationInventoryAvailable>false</IsMultiLocationInventoryAvailable>
+<IsMultiLocationInventoryEnabled>false</IsMultiLocationInventoryEnabled>
+</MultiLocationInventoryPreferences>
+<PurchasesAndVendorsPreferences>
+<IsUsingInventory>false</IsUsingInventory>
+<DaysBillsAreDue>10</DaysBillsAreDue>
+<IsAutomaticallyUsingDiscounts>false</IsAutomaticallyUsingDiscounts>
+</PurchasesAndVendorsPreferences>
+<ReportsPreferences>
+<AgingReportBasis>AgeFromDueDate</AgingReportBasis>
+<SummaryReportBasis>Accrual</SummaryReportBasis>
+</ReportsPreferences>
+<SalesAndCustomersPreferences>
+<IsTrackingReimbursedExpensesAsIncome>false</IsTrackingReimbursedExpensesAsIncome>
+<IsAutoApplyingPayments>true</IsAutoApplyingPayments>
+<PriceLevels>
+<IsUsingPriceLevels>true</IsUsingPriceLevels>
+<IsRoundingSalesPriceUp>true</IsRoundingSalesPriceUp>
+</PriceLevels>
+</SalesAndCustomersPreferences>
+<TimeTrackingPreferences>
+<FirstDayOfWeek>Monday</FirstDayOfWeek>
+</TimeTrackingPreferences>
+<CurrentAppAccessRights>
+<IsAutomaticLoginAllowed>false</IsAutomaticLoginAllowed>
+<IsPersonalDataAccessAllowed>false</IsPersonalDataAccessAllowed>
+</CurrentAppAccessRights>
+<ItemsAndInventoryPreferences>
+<EnhancedInventoryReceivingEnabled>false</EnhancedInventoryReceivingEnabled>
+<IsTrackingSerialOrLotNumber>None</IsTrackingSerialOrLotNumber>
+<FIFOEnabled>false</FIFOEnabled>
+<IsRSBEnabled>false</IsRSBEnabled>
+<IsBarcodeEnabled>false</IsBarcodeEnabled>
+</ItemsAndInventoryPreferences>
+</PreferencesRet>
+</PreferencesQueryRs>
+</QBXMLMsgsRs>
+</QBXML>
+ C:\Users\Public\Documents\Intuit\QuickBooks\Company Files\ACME LIMITED.qbw US 13 0
\ No newline at end of file
diff --git a/tests/Functional/fixtures/transaction.csv b/tests/Functional/fixtures/transaction.csv
new file mode 100644
index 0000000..4b69479
--- /dev/null
+++ b/tests/Functional/fixtures/transaction.csv
@@ -0,0 +1,4 @@
+txnDate,refNumber,currency,exchangeRate,creditAccount,creditMemo,creditAmount,debitAccount,debitMemo,debitAmount
+2018-10-10,ref1,"US Dollar",7.83704,"Bank USD","credit memo",400.00,"Uncategorized Expenses","debit memo",400.00
+2018-10-10,ref21,"US Dollar",7.83704,"Bank USD","credit memo",500.00,"Undeposited Funds","debit memo",500.00
+2018-10-10,ref22,Euro,9.0126,"Undeposited Funds","credit memo",433.33,"Bank EUR","debit memo",433.33
diff --git a/tests/Functional/fixtures/transaction_converted.xml b/tests/Functional/fixtures/transaction_converted.xml
new file mode 100644
index 0000000..2a73424
--- /dev/null
+++ b/tests/Functional/fixtures/transaction_converted.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+ 2018-10-10
+ ref1
+
+ US Dollar
+
+ 7.83704
+
+
+ Uncategorized Expenses
+
+ 400.00
+ debit memo
+
+
+
+ Bank USD
+
+ 400.00
+ credit memo
+
+
+
+
+
+ 2018-10-10
+ ref21
+
+ US Dollar
+
+ 7.83704
+
+
+ Undeposited Funds
+
+ 500.00
+ debit memo
+
+
+
+ Bank USD
+
+ 500.00
+ credit memo
+
+
+
+
+
+ 2018-10-10
+ ref22
+
+ Euro
+
+ 9.0126
+
+
+ Bank EUR
+
+ 433.33
+ debit memo
+
+
+
+ Undeposited Funds
+
+ 433.33
+ credit memo
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Functional/fixtures/vendor.csv b/tests/Functional/fixtures/vendor.csv
new file mode 100644
index 0000000..8f5299b
--- /dev/null
+++ b/tests/Functional/fixtures/vendor.csv
@@ -0,0 +1,2 @@
+vendorFullname,vendorCompanyName,addr1,addr2,vendorType,terms,currency,city,state,postalcode,country
+Silo,"Silo LIMITED","68 Tap Kwok Nam Path","Lok Sheuk Tsan","Service Providers","Due on receipt","Hong Kong Dollar","Hong Kong",HK,999077,"Hong Kong"
diff --git a/tests/Functional/fixtures/vendor_converted.xml b/tests/Functional/fixtures/vendor_converted.xml
new file mode 100644
index 0000000..55614db
--- /dev/null
+++ b/tests/Functional/fixtures/vendor_converted.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ Silo
+ Silo LIMITED
+
+ 68 Tap Kwok Nam Path
+ Lok Sheuk Tsan
+ Hong Kong
+ HK
+ 999077
+ Hong Kong
+
+
+ Service Providers
+
+
+ Due on receipt
+
+
+ Hong Kong Dollar
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Unit/Accounts/AccountsUpdaterTest.php b/tests/Unit/Accounts/AccountsUpdaterTest.php
new file mode 100644
index 0000000..d31eec7
--- /dev/null
+++ b/tests/Unit/Accounts/AccountsUpdaterTest.php
@@ -0,0 +1,75 @@
+server = $this->getMockBuilder(QuickbooksServerInterface::class)->getMock();
+ $this->accountRepo = $this->getMockBuilder(QuickbooksAccountRepositoryInterface::class)->getMock();
+ $this->companyRepo = $this->getMockBuilder(QuickbooksCompanyRepositoryInterface::class)->getMock();
+ $this->em = $this->getMockBuilder(EntityManagerInterface::class)->getMock();
+ $this->em->method('getRepository')->willReturnMap([
+ [QuickbooksAccount::class, $this->accountRepo],
+ [QuickbooksCompany::class, $this->companyRepo],
+ ]);
+
+
+ $this->updater = new AccountsUpdater(
+ new QuickbooksFormatter(),
+ $this->server,
+ $this->em
+ );
+ }
+
+
+ public function testSchedule(): void
+ {
+ $this->server->expects(self::once())->method('schedule')
+ ->with('user_test', QUICKBOOKS_QUERY_ACCOUNT, AccountsUpdater::ACCOUNTS_UPDATE_REQUEST_ID)
+ ->willReturn(true);
+ self::assertTrue($this->updater->scheduleUpdate('user_test'));
+ }
+
+ public function testUpdate(): void
+ {
+ $this->accountRepo->expects(self::once())->method('deleteAll');
+
+ $user = new User();
+ $user->setId(999);
+ $company = new QuickbooksCompany('test_user');
+ $company->setUser($user);
+ $this->companyRepo->method('findOneBy')->willReturn($company);
+
+ $this->em->expects(self::atLeastOnce())->method('persist')
+ ->with(self::isInstanceOf(QuickbooksAccount::class));
+ $this->em->expects(self::once())->method('flush');
+
+ $xml = file_get_contents(__DIR__.'/chart_of_accounts.xml');
+ $this->updater->update('test_user', $xml);
+ }
+}
diff --git a/tests/Unit/Accounts/chart_of_accounts.xml b/tests/Unit/Accounts/chart_of_accounts.xml
new file mode 100644
index 0000000..3116e10
--- /dev/null
+++ b/tests/Unit/Accounts/chart_of_accounts.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+ 80000028-1560778874
+ 2019-06-17T06:41:14-08:00
+ 2019-07-22T11:03:01-08:00
+ 1560778874
+ Bank EUR
+ Bank EUR
+ true
+ 0
+ Bank
+ 0.00
+ 0.00
+
+ 1864
+ B/S-Assets: Cash
+
+ NotApplicable
+
+ 80000033-1560777868
+ Euro
+
+
+
+ 80000027-1560778661
+ 2019-06-17T06:37:41-08:00
+ 2019-07-22T11:03:15-08:00
+ 1560778661
+ Bank USD
+ Bank USD
+ true
+ 0
+ Bank
+ 0.00
+ 0.00
+
+ 1864
+ B/S-Assets: Cash
+
+ NotApplicable
+
+ 80000096-1560777869
+ US Dollar
+
+
+
+ 8000002D-1560783117
+ 2019-06-17T07:51:57-08:00
+ 2019-06-17T07:51:57-08:00
+ 1560783117
+ Accounts Receivable
+ Accounts Receivable
+ true
+ 0
+ AccountsReceivable
+ AccountsReceivable
+ 11000
+ Unpaid or unapplied customer invoices and credits
+ 0.00
+ 0.00
+ Operating
+
+ 8000003F-1560777868
+ Hong Kong Dollar
+
+
+
+ 8000002E-1560783173
+ 2019-06-17T07:52:53-08:00
+ 2019-07-21T07:06:24-08:00
+ 1560783173
+ Accounts Receivable - USD
+ Accounts Receivable - USD
+ true
+ 0
+ AccountsReceivable
+ AccountsReceivable
+ 11001
+ Unpaid or unapplied customer invoices and credits
+ 0.00
+ 0.00
+ Operating
+
+ 80000096-1560777869
+ US Dollar
+
+
+
+
+
diff --git a/tests/Unit/Currency/UpdateRateOnEntityScheduledSubscriberTest.php b/tests/Unit/Currency/UpdateRateOnEntityScheduledSubscriberTest.php
new file mode 100644
index 0000000..a2145b6
--- /dev/null
+++ b/tests/Unit/Currency/UpdateRateOnEntityScheduledSubscriberTest.php
@@ -0,0 +1,71 @@
+user = new QuickbooksCompany(self::TEST_USER);
+ $this->user->setBaseCurrency('HKD');
+ $this->exchanger = $this->getMockBuilder(CurrencyExchangerInterface::class)->getMock();
+ $this->subscriber = new UpdateRateOnEntityScheduledSubscriber($this->exchanger);
+ }
+
+ public function testSetExchangeRateIfNotSet(): void
+ {
+ $t1 = new Transaction();
+ $t1->setTxnDate('2018-10-10');
+ $t1->setCurrency('US Dollar');
+
+ $this->exchanger->expects(self::once())->method('getExchangeRate')
+ ->with('HKD', 'USD', '2018-10-10')
+ ->willReturn(8);
+ $event = new EntityOnScheduledEvent($this->user, [$t1], 0);
+ $this->subscriber->onScheduled($event);
+
+ self::assertSame('8', $t1->getExchangeRate());
+ }
+
+ public function testSkipsIfExchangeRateIsSet(): void
+ {
+ $t1 = new Transaction();
+ $t1->setTxnDate('2018-10-10');
+ $t1->setCurrency('US Dollar');
+ $t1->setExchangeRate('8');
+
+ $this->exchanger->expects(self::never())->method('getExchangeRate');
+
+ $event = new EntityOnScheduledEvent($this->user, [$t1], 0);
+ $this->subscriber->onScheduled($event);
+
+ self::assertSame('8', $t1->getExchangeRate());
+ }
+
+ public function testIrrelevantObjectsSkipped(): void
+ {
+ $obj = new \stdClass();
+ $obj->a = 'b';
+
+ $this->exchanger->expects(self::never())->method('getExchangeRate');
+ $event = new EntityOnScheduledEvent($this->user, [$obj], 0);
+ $this->subscriber->onScheduled($event);
+ }
+}
diff --git a/tests/Unit/EventSubscriber/UpdateCompanySubscriberTest.php b/tests/Unit/EventSubscriber/UpdateCompanySubscriberTest.php
new file mode 100644
index 0000000..cea47d2
--- /dev/null
+++ b/tests/Unit/EventSubscriber/UpdateCompanySubscriberTest.php
@@ -0,0 +1,108 @@
+companyRepo = $this->getMockBuilder(QuickbooksCompanyRepositoryInterface::class)->getMock();
+ $this->em = $this->getMockBuilder(EntityManagerInterface::class)->getMock();
+ $this->subscriber = new UpdateCompanySubscriber($this->companyRepo, $this->em);
+ $this->xml = file_get_contents(__DIR__.'/../../Functional/fixtures/company_query_rs.xml');
+ Assert::string($this->xml);
+ }
+
+ public function testOnSendRequestXmlEvent()
+ {
+ $this->em->expects(self::once())->method('flush');
+ $this->companyRepo->expects(self::once())->method('findOneBy')
+ ->willReturn($c = new QuickbooksCompany());
+
+ $c->setMultiCurrencyEnabled(true);
+ $c->setDecimalSymbol('.');
+ $c->setDigitGroupingSymbol(',');
+ $c->setQbCompanyFile(null);
+
+ $this->subscriber->onSendRequestXmlEvent($this->getEvent($this->xml));
+ Assert::false($c->isMultiCurrencyEnabled());
+ Assert::same(',', $c->getDecimalSymbol());
+ Assert::same('.', $c->getDigitGroupingSymbol());
+ Assert::same('C:\\Users\\Public\\Documents\\Intuit\\QuickBooks\\Company Files\\Acme Inc.qbw', $c->getQbCompanyFile());
+ }
+
+ public function testDoesNothingIfNoCompany()
+ {
+ $this->em->expects(self::never())->method('flush');
+ $this->companyRepo->expects(self::once())->method('findOneBy')->willReturn(null);
+
+ $this->subscriber->onSendRequestXmlEvent($this->getEvent($this->xml));
+ }
+
+ public function testDoesNothingOnInvalidXML()
+ {
+ $this->em->expects(self::never())->method('flush');
+ $this->companyRepo->expects(self::once())->method('findOneBy')
+ ->willReturn($c = new QuickbooksCompany());
+
+ $xml = 'invalid';
+ $this->subscriber->onSendRequestXmlEvent($this->getEvent($xml));
+ }
+
+ public function testDoesNothingOnIncorrectXML()
+ {
+ $this->em->expects(self::never())->method('flush');
+ $this->companyRepo->expects(self::once())->method('findOneBy')
+ ->willReturn($c = new QuickbooksCompany());
+
+ $xml = '';
+ $this->subscriber->onSendRequestXmlEvent($this->getEvent($xml));
+ }
+
+ public function testGetDecimalSymbol()
+ {
+ self::assertSame('.', $this->subscriber->getDecimalSymbol('100,000.00'));
+ self::assertSame(',', $this->subscriber->getDecimalSymbol('100.000,00'));
+ self::assertSame(',', $this->subscriber->getDecimalSymbol('100,00'));
+ self::assertSame('.', $this->subscriber->getDecimalSymbol('100.00'));
+ self::assertSame('.', $this->subscriber->getDecimalSymbol('0'));
+ self::assertSame('.', $this->subscriber->getDecimalSymbol(''));
+ self::assertSame('.', $this->subscriber->getDecimalSymbol(null));
+ }
+
+ private function getEvent(?string $xml): QuickbooksServerSendRequestXmlEvent
+ {
+ \QuickBooks_WebConnector_Handlers::HOOK_AUTHENTICATE; //hack to load constants
+ return new QuickbooksServerSendRequestXmlEvent(null, self::USERNAME, QUICKBOOKS_HANDLERS_HOOK_SENDREQUESTXML, '', [
+ 'username' => self::USERNAME,
+ 'ticket' => '20bdc17a-83aa-2de4-ad26-0614439c0391',
+ 'strHCPResponse' => $xml,
+ 'strCompanyFileName' => 'C:\\Users\\Public\\Documents\\Intuit\\QuickBooks\\Company Files\\Acme Inc.qbw',
+ 'qbXMLCountry' => 'US',
+ 'qbXMLMajorVers' => '13',
+ 'qbXMLMinorVers' => '0',
+ 'requestID' => null,
+ 'user' => self::USERNAME,
+ ], []);
+ }
+}
diff --git a/tests/Unit/SheetScheduler/CustomerTest.php b/tests/Unit/SheetScheduler/CustomerTest.php
new file mode 100644
index 0000000..a3e8de8
--- /dev/null
+++ b/tests/Unit/SheetScheduler/CustomerTest.php
@@ -0,0 +1,125 @@
+customer = new Customer();
+ $this->customer->setAddr1(self::ADDR_1);
+ $this->customer->setAddr2(self::ADDR_2);
+ $this->customer->setCity(self::CITY);
+ $this->customer->setState(self::STATE);
+ $this->customer->setPostalcode(self::ZIP);
+ $this->customer->setCountry(self::COUNTRY);
+ $this->customer->setCompanyName('');
+ $this->customer->setFirstName('');
+ $this->customer->setLastName('');
+ }
+
+ public function testWithoutCompanyAndName(): void
+ {
+ self::assertSame([
+ self::ADDR_1,
+ self::ADDR_2,
+ '',
+ '',
+ '',
+ self::CITY,
+ self::STATE,
+ '',
+ self::ZIP,
+ self::COUNTRY,
+ ], $this->customer->composeBillAddress());
+ }
+
+ public function testWithCompanyAndName(): void
+ {
+ $this->customer->setCompanyName('Company');
+ $this->customer->setFirstName('First');
+ $this->customer->setLastName('Last');
+
+ self::assertSame([
+ 'Attn: First Last',
+ 'Company',
+ self::ADDR_1,
+ self::ADDR_2,
+ '',
+ self::CITY,
+ self::STATE,
+ '',
+ self::ZIP,
+ self::COUNTRY,
+ ], $this->customer->composeBillAddress());
+ }
+
+ public function testWithCompanyWithoutName(): void
+ {
+ $this->customer->setCompanyName('Company');
+
+ self::assertSame([
+ 'Company',
+ self::ADDR_1,
+ self::ADDR_2,
+ '',
+ '',
+ self::CITY,
+ self::STATE,
+ '',
+ self::ZIP,
+ self::COUNTRY,
+ ], $this->customer->composeBillAddress());
+ }
+
+ public function testWithCompanyAndFirstName(): void
+ {
+ $this->customer->setCompanyName('Company');
+ $this->customer->setFirstName('First');
+
+ self::assertSame([
+ 'Attn: First',
+ 'Company',
+ self::ADDR_1,
+ self::ADDR_2,
+ '',
+ self::CITY,
+ self::STATE,
+ '',
+ self::ZIP,
+ self::COUNTRY,
+ ], $this->customer->composeBillAddress());
+ }
+
+ public function testWithCompanyAndLastName(): void
+ {
+ $this->customer->setCompanyName('Company');
+ $this->customer->setLastName('Last');
+
+ self::assertSame([
+ 'Attn: Last',
+ 'Company',
+ self::ADDR_1,
+ self::ADDR_2,
+ '',
+ self::CITY,
+ self::STATE,
+ '',
+ self::ZIP,
+ self::COUNTRY,
+ ], $this->customer->composeBillAddress());
+ }
+}
diff --git a/tests/Unit/SheetScheduler/LineItemLogicTest.php b/tests/Unit/SheetScheduler/LineItemLogicTest.php
new file mode 100644
index 0000000..104c58e
--- /dev/null
+++ b/tests/Unit/SheetScheduler/LineItemLogicTest.php
@@ -0,0 +1,24 @@
+expectExceptionMessage("Amount is required");
+ $this->expectException(RuntimeException::class);
+ LineItemLogic::getQuantity('', '', '');
+ }
+}
diff --git a/tests/Unit/SheetScheduler/SplitTransactionsSubscriberTest.php b/tests/Unit/SheetScheduler/SplitTransactionsSubscriberTest.php
new file mode 100644
index 0000000..b864356
--- /dev/null
+++ b/tests/Unit/SheetScheduler/SplitTransactionsSubscriberTest.php
@@ -0,0 +1,163 @@
+user = new QuickbooksCompany(self::TEST_USER);
+ $this->accountRepo = $this->getMockBuilder(QuickbooksAccountRepositoryInterface::class)->getMock();
+ $this->em = $this->getMockBuilder(EntityManagerInterface::class)->getMock();
+ $this->em->method('getRepository')->willReturnMap([
+ [QuickbooksAccount::class, $this->accountRepo],
+ ]);
+
+ $this->subscriber = new SplitTransactionsSubscriber($this->em);
+ }
+
+ public function testIrrelevantObjects(): void
+ {
+ $obj = new \stdClass();
+ $obj->a = 'b';
+
+ $event = new EntityOnScheduledEvent($this->user, [$obj], 0);
+ $this->subscriber->onScheduled($event);
+
+ self::assertSame([$obj], $event->getEntities());
+ }
+
+ public function testTransferSameCurrency(): void
+ {
+ $transaction = new Transaction();
+ $transaction->setTxnDate('2018-10-10');
+ $transaction->setRefNumber('ref1');
+ $transaction->setCurrency('US Dollar');
+ $transaction->setExchangeRate('7.83704');
+ $transaction->setCreditAccount('Bank1 USD');
+ $transaction->setCreditMemo('credit memo');
+ $transaction->setCreditAmount('400.00');
+ $transaction->setDebitAccount('Bank2 USD');
+ $transaction->setDebitMemo('debit memo');
+ $transaction->setDebitAmount('400.00');
+
+ $event = new EntityOnScheduledEvent($this->user, [$transaction], 0);
+ $this->subscriber->onScheduled($event);
+
+ self::assertSame([$transaction], $event->getEntities());
+ }
+
+ public function testTransferDifferentCurrency(): void
+ {
+ $transaction = new Transaction();
+ $transaction->setTxnDate('2018-10-10');
+ $transaction->setRefNumber('ref1');
+ $transaction->setCurrency('US Dollar');
+ $transaction->setCreditAccount('Bank USD');
+ $transaction->setCreditMemo('credit memo');
+ $transaction->setCreditAmount('500.00');
+ $transaction->setDebitAccount('Bank EUR');
+ $transaction->setDebitMemo('debit memo');
+ $transaction->setDebitAmount('433.33');
+
+ $this->accountRepo->method('getCurrency')->willReturnMap([
+ [self::TEST_USER, 'Bank USD', QuickbooksAccount::TYPE_BANK, 'US Dollar'],
+ [self::TEST_USER, 'Bank EUR', QuickbooksAccount::TYPE_BANK, 'Euro'],
+ ]);
+
+
+ $event = new EntityOnScheduledEvent($this->user, [$transaction], 0);
+ $this->subscriber->onScheduled($event);
+
+ self::assertCount(2, $event->getEntities());
+ [$e1, $e2] = $event->getEntities();
+ $normalizer = new ObjectNormalizer();
+ $actual = $normalizer->normalize($e1);
+ $expected = [
+ 'txnDate' => '2018-10-10',
+ 'refNumber' => 'ref1',
+ 'currency' => 'US Dollar',
+ 'exchangeRate' => null,
+ 'creditAccount' => 'Bank USD',
+ 'creditMemo' => 'credit memo',
+ 'creditAmount' => '500.00',
+ 'debitAccount' => QuickbooksAccount::UNDEPOSITED_FUNDS,
+ 'debitMemo' => 'debit memo',
+ 'debitAmount' => '500.00',
+ ];
+ self::assertEquals($expected, $actual);
+
+ $actual = $normalizer->normalize($e2);
+ $expected = [
+ 'txnDate' => '2018-10-10',
+ 'refNumber' => 'ref1',
+ 'currency' => 'Euro',
+ 'exchangeRate' => SplitTransactionsSubscriber::ID,
+ 'creditAccount' => QuickbooksAccount::UNDEPOSITED_FUNDS,
+ 'creditMemo' => 'credit memo',
+ 'creditAmount' => '433.33',
+ 'debitAccount' => 'Bank EUR',
+ 'debitMemo' => 'debit memo',
+ 'debitAmount' => '433.33',
+ ];
+ self::assertEquals($expected, $actual);
+ }
+
+ public function testUpdatesReversedRate(): void
+ {
+ $t1 = new Transaction();
+ $t1->setTxnDate('2018-10-10');
+ $t1->setCurrency('US Dollar');
+ $t1->setCreditAccount('Bank USD');
+ $t1->setCreditAmount('500.00');
+ $t1->setDebitAccount(QuickbooksAccount::UNDEPOSITED_FUNDS);
+ $t1->setDebitAmount('500.00');
+ $t1->setExchangeRate('8');
+
+ $t2 = new Transaction();
+ $t2->setTxnDate('2018-10-10');
+ $t2->setCurrency('Euro');
+ $t2->setCreditAccount(QuickbooksAccount::UNDEPOSITED_FUNDS);
+ $t2->setCreditAmount('433.33');
+ $t2->setDebitAccount('Bank EUR');
+ $t2->setDebitAmount('433.33');
+ $t2->setExchangeRate(SplitTransactionsSubscriber::ID);
+
+ $this->accountRepo->method('getCurrency')->willReturnMap([
+ [self::TEST_USER, 'Bank USD', QuickbooksAccount::TYPE_BANK, 'US Dollar'],
+ [self::TEST_USER, 'Bank EUR', QuickbooksAccount::TYPE_BANK, 'Euro'],
+ ]);
+
+ $event = new EntityOnScheduledEvent($this->user, [$t1, $t2], 0);
+ $this->subscriber->afterExchangeRateUpdated($event);
+
+ self::assertCount(2, $event->getEntities());
+ /** @var Transaction[] $entities */
+ $entities = $event->getEntities();
+ [$e1, $e2] = $entities;
+ self::assertSame('9.2308402372326', $e2->getExchangeRate());
+ }
+}
diff --git a/tests/Unit/SheetScheduler/Transformer/TransactionTransformerTest.php b/tests/Unit/SheetScheduler/Transformer/TransactionTransformerTest.php
new file mode 100644
index 0000000..3a835ae
--- /dev/null
+++ b/tests/Unit/SheetScheduler/Transformer/TransactionTransformerTest.php
@@ -0,0 +1,123 @@
+setMultiCurrencyEnabled(true);
+ $this->company = $company;
+
+ $this->transformer = new TransactionTransformer();
+ $this->transformer->setCompany($this->company);
+ }
+
+ public function testExpense(): void
+ {
+ $transaction = new Transaction();
+ $transaction->setTxnDate('2018-10-10');
+ $transaction->setRefNumber('ref1');
+ $transaction->setCurrency('US Dollar');
+ $transaction->setExchangeRate('7.83704');
+ $transaction->setCreditAccount('Bank USD');
+ $transaction->setCreditMemo('credit memo');
+ $transaction->setCreditAmount('1,400.00');
+ $transaction->setDebitAccount(QuickbooksAccount::UNCATEGORIZED_EXPENSES);
+ $transaction->setDebitMemo('debit memo');
+ $transaction->setDebitAmount('1,400.00');
+
+ $results = $this->transformer->transform($transaction);
+ self::assertCount(1, $results);
+ /** @var \QuickBooks_QBXML_Object_JournalEntry $object */
+ [$action, $object] = $results[0];
+ self::assertSame(QUICKBOOKS_ADD_JOURNALENTRY, $action);
+ $actual = $this->toArray($object->asList(null));
+ $expected = [
+ 'TxnDate' => '2018-10-10',
+ 'RefNumber' => 'ref1',
+ 'ExchangeRate' => '7.83704',
+ 'CurrencyRef FullName' => 'US Dollar',
+ 'JournalCreditLine' => [[
+ 'AccountRef FullName' => 'Bank USD',
+ 'Amount' => '1400.00',
+ 'Memo' => 'credit memo',
+ ]],
+ 'JournalDebitLine' => [[
+ 'AccountRef FullName' => QuickbooksAccount::UNCATEGORIZED_EXPENSES,
+ 'Amount' => '1400.00',
+ 'Memo' => 'debit memo',
+ ]],
+ ];
+ self::assertEquals($expected, $actual);
+ }
+
+ public function testTransferSameCurrency(): void
+ {
+ $transaction = new Transaction();
+ $transaction->setTxnDate('2018-10-10');
+ $transaction->setRefNumber('ref1');
+ $transaction->setCurrency('US Dollar');
+ $transaction->setExchangeRate('7.83704');
+ $transaction->setCreditAccount('Bank1 USD');
+ $transaction->setCreditMemo('credit memo');
+ $transaction->setCreditAmount('400.00');
+ $transaction->setDebitAccount('Bank2 USD');
+ $transaction->setDebitMemo('debit memo');
+ $transaction->setDebitAmount('400.00');
+
+ $results = $this->transformer->transform($transaction);
+ self::assertCount(1, $results);
+ /** @var \QuickBooks_QBXML_Object_JournalEntry $object */
+ [$action, $object] = $results[0];
+ self::assertSame(QUICKBOOKS_ADD_JOURNALENTRY, $action);
+ $actual = $this->toArray($object->asList(null));
+ $expected = [
+ 'TxnDate' => '2018-10-10',
+ 'RefNumber' => 'ref1',
+ 'ExchangeRate' => '7.83704',
+ 'CurrencyRef FullName' => 'US Dollar',
+ 'JournalCreditLine' => [[
+ 'AccountRef FullName' => 'Bank1 USD',
+ 'Amount' => '400.00',
+ 'Memo' => 'credit memo',
+ ]],
+ 'JournalDebitLine' => [[
+ 'AccountRef FullName' => 'Bank2 USD',
+ 'Amount' => '400.00',
+ 'Memo' => 'debit memo',
+ ]],
+ ];
+ self::assertEquals($expected, $actual);
+ }
+
+
+ private function toArray(array $list): array
+ {
+ $array = [];
+
+ foreach ($list as $key => $value) {
+ if ($value instanceof QuickBooks_QBXML_Object) {
+ $array[$key] = $this->toArray($value->asList(null));
+ } else if (is_array($value)) {
+ $array[$key] = $this->toArray($value);
+ } else {
+ $array[$key] = $value;
+ }
+ }
+ return $array;
+ }
+}
diff --git a/tests/Unit/SheetScheduler/Transformer/TransformerContextTraitTest.php b/tests/Unit/SheetScheduler/Transformer/TransformerContextTraitTest.php
new file mode 100644
index 0000000..3ecfb93
--- /dev/null
+++ b/tests/Unit/SheetScheduler/Transformer/TransformerContextTraitTest.php
@@ -0,0 +1,53 @@
+company = new QuickbooksCompany('test');
+ }
+
+ public function testDefaultMultiCurrencyDisabled()
+ {
+ self::assertFalse($this->isMultiCurrencyEnabled());
+ }
+
+ public function testMultiCurrencyEnabled()
+ {
+ $this->company->setMultiCurrencyEnabled(true);
+ self::assertTrue($this->isMultiCurrencyEnabled());
+ }
+
+ public function testNoCompanyMultiCurrencyDisabled()
+ {
+ $this->company = null;
+ self::assertFalse($this->isMultiCurrencyEnabled());
+ }
+
+ public function testDefaultUsedDecimalSymbolDot()
+ {
+ self::assertSame('100.00', $this->getAmount('100.00'));
+ self::assertSame('0.00',$this->getAmount('0.00'));
+ self::assertSame('0', $this->getAmount('0'));
+ }
+
+ public function testUsedDecimalSymbolComma()
+ {
+ $this->company->setDecimalSymbol(',');
+
+ self::assertSame('100,00', $this->getAmount('100.00'));
+ self::assertSame('0,00',$this->getAmount('0.00'));
+ self::assertSame('0', $this->getAmount('0'));
+ }
+}
diff --git a/tests/Unit/TransactionsConverterTest.php b/tests/Unit/TransactionsConverterTest.php
new file mode 100644
index 0000000..e56dc67
--- /dev/null
+++ b/tests/Unit/TransactionsConverterTest.php
@@ -0,0 +1,394 @@
+csvEncoder = new CsvEncoder();
+ $this->accountRepository = $this->getMockBuilder(QuickbooksAccountRepositoryInterface::class)->getMock();
+ $serializer = new Serializer([new ObjectNormalizer()], [$this->csvEncoder]);
+ $this->converter = new TransactionsConverter($this->csvEncoder, $serializer, $this->accountRepository, __DIR__ . '/fixtures/accounts_mapping.json');
+
+ $this->accountRepository->method('findOneByName')->willReturnCallback(function ($qbUsername, $name): ?QuickbooksAccount {
+ $acc = new QuickbooksAccount();
+ $acc->setCompany(new QuickbooksCompany($qbUsername));
+ $acc->setFullName($name);
+ switch ($name) {
+ case 'HDFC HKD Savings':
+ $acc->setCurrency('Hong Kong Dollar');
+ $acc->setAccountType(QuickbooksAccount::TYPE_BANK);
+ break;
+ case 'Bank Service Charges':
+ $acc->setCurrency('Hong Kong Dollar');
+ $acc->setAccountType(QuickbooksAccount::TYPE_EXPENSE);
+ break;
+ case 'Citibank EUR':
+ $acc->setCurrency('Euro');
+ $acc->setAccountType(QuickbooksAccount::TYPE_BANK);
+ break;
+ case 'Citibank USD':
+ $acc->setCurrency('US Dollar');
+ $acc->setAccountType(QuickbooksAccount::TYPE_BANK);
+ break;
+ case 'Interest Income':
+ $acc->setCurrency('Hong Kong Dollar');
+ $acc->setAccountType(QuickbooksAccount::TYPE_OTHER_INCOME);
+ break;
+ case 'Uncategorized Income':
+ $acc->setCurrency('Hong Kong Dollar');
+ $acc->setAccountType(QuickbooksAccount::TYPE_INCOME);
+ break;
+ default:
+ return null;
+ }
+ return $acc;
+ });
+ }
+
+ public function testConvertExpenseHkd(): void
+ {
+ $input = [
+ [
+ 'Date' => '07/04/18',
+ 'Transaction ID' => '5ba07172d1b673a5523cff1e26a9bb2f',
+ 'Number' => '',
+ 'Description' => 'Monthly fee',
+ 'Notes' => '',
+ 'Commodity/Currency' => 'CURRENCY::HKD',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Assets:Current Assets:HDFC Savings',
+ 'Account Name' => 'HDFC Savings',
+ 'Amount With Sym' => '-HK$1,200.00',
+ 'Amount Num' =>
+ [
+ '' => '-1,200.00',
+ ],
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '1.00',
+ ],
+ [
+ 'Date' => '',
+ 'Transaction ID' => '',
+ 'Number' => '',
+ 'Description' => '',
+ 'Notes' => '',
+ 'Commodity/Currency' => '',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Expense:Bank Charges HKD',
+ 'Account Name' => 'Bank Charges HKD',
+ 'Amount With Sym' => 'HK$1,200.00',
+ 'Amount Num' =>
+ [
+ '' => '1,200.00',
+ ],
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '1.00',
+ ]
+ ];
+
+ $output = $this->converter->convert($input, self::COMPANY1);
+
+ self::assertSame([
+ [
+ 'txnDate' => '2018-04-07',
+ 'refNumber' => NULL,
+ 'currency' => 'Hong Kong Dollar',
+ 'exchangeRate' => NULL,
+ 'creditAccount' => 'HDFC HKD Savings',
+ 'creditMemo' => 'Monthly fee',
+ 'creditAmount' => '1,200.00',
+ 'debitAccount' => 'Bank Service Charges',
+ 'debitMemo' => 'Monthly fee',
+ 'debitAmount' => '1,200.00',
+ ]
+ ], $output);
+ }
+
+ public function testConvertBankToBankTransferUsdToEuro(): void
+ {
+ $input = [
+ [
+ 'Date' => '17/01/19',
+ 'Transaction ID' => '3a37539cccd9465622266a2444dab907',
+ 'Number' => '',
+ 'Description' => 'Line Management / Negative Balance Balancing - 10300002-13045786-00014885 EUR ACME LIMITED',
+ 'Notes' => '',
+ 'Commodity/Currency' => 'CURRENCY::USD',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Assets:Current Assets:Citibank EUR',
+ 'Account Name' => 'Citibank EUR',
+ 'Amount With Sym' => '€9.80',
+ 'Amount Num' =>
+ [
+ '' => '9.80',
+ ],
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '1 + 32/245',
+ ],
+ [
+ 'Date' => '',
+ 'Transaction ID' => '',
+ 'Number' => '',
+ 'Description' => '',
+ 'Notes' => '',
+ 'Commodity/Currency' => '',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Assets:Current Assets:Citibank USD',
+ 'Account Name' => 'Citibank USD',
+ 'Amount With Sym' => '-$11.08',
+ 'Amount Num' =>
+ [
+ '' => '-11.08',
+ ],
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '1.00',
+ ]
+ ];
+
+ $output = $this->converter->convert($input, self::COMPANY2);
+
+ self::assertSame([
+ [
+ 'txnDate' => '2019-01-17',
+ 'refNumber' => NULL,
+ 'currency' => 'Euro',
+ 'exchangeRate' => NULL,
+ 'creditAccount' => 'Citibank USD',
+ 'creditMemo' => 'Line Management / Negative Balance Balancing - 10300002-13045786-00014885 EUR ACME LIMITED',
+ 'creditAmount' => '11.08',
+ 'debitAccount' => 'Citibank EUR',
+ 'debitMemo' => 'Line Management / Negative Balance Balancing - 10300002-13045786-00014885 EUR ACME LIMITED',
+ 'debitAmount' => '9.80',
+ ]
+ ], $output);
+ }
+
+ public function testConvertUsdExpenseFromEuroAccount(): void
+ {
+ $input = [
+ array (
+ 'Date' => '28/12/18',
+ 'Transaction ID' => '9d4f8a03907dccbc2af3fea2bcc238a6',
+ 'Number' => '',
+ 'Description' => 'Account management fee',
+ 'Notes' => '',
+ 'Commodity/Currency' => 'CURRENCY::EUR',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Assets:Current Assets:Citibank EUR',
+ 'Account Name' => 'Citibank EUR',
+ 'Amount With Sym' => '-€9.80',
+ 'Amount Num' =>
+ array (
+ '' => '-9.80',
+ ),
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '1.00',
+ ),
+ array (
+ 'Date' => '',
+ 'Transaction ID' => '',
+ 'Number' => '',
+ 'Description' => '',
+ 'Notes' => '',
+ 'Commodity/Currency' => '',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Expense:Bank Charges USD',
+ 'Account Name' => 'Bank Charges USD',
+ 'Amount With Sym' => '$11.65',
+ 'Amount Num' =>
+ array (
+ '' => '11.65',
+ ),
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '196/233',
+ )
+ ];
+
+ $output = $this->converter->convert($input, self::COMPANY2);
+
+ self::assertSame([
+ [
+ 'txnDate' => '2018-12-28',
+ 'refNumber' => NULL,
+ 'currency' => 'Euro',
+ 'exchangeRate' => NULL,
+ 'creditAccount' => 'Citibank EUR',
+ 'creditMemo' => 'Account management fee',
+ 'creditAmount' => '9.80',
+ 'debitAccount' => 'Bank Service Charges',
+ 'debitMemo' => 'Account management fee',
+ 'debitAmount' => '9.80',
+ ]
+ ], $output);
+ }
+
+ public function testConvertIncomeHKD(): void
+ {
+ $input = [
+ [
+ 'Date' => '28/09/18',
+ 'Transaction ID' => '25d6e938c44fdea473fa25d0f879e4fb',
+ 'Number' => '',
+ 'Description' => 'CREDIT INTEREST',
+ 'Notes' => '',
+ 'Commodity/Currency' => 'CURRENCY::HKD',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Assets:Current Assets:HDFC Savings',
+ 'Account Name' => 'HDFC Savings',
+ 'Amount With Sym' => 'HK$0.01',
+ 'Amount Num' =>
+ [
+ '' => '0.01',
+ ],
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '1.00',
+ ],
+ [
+ 'Date' => '',
+ 'Transaction ID' => '',
+ 'Number' => '',
+ 'Description' => '',
+ 'Notes' => '',
+ 'Commodity/Currency' => '',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Income:Interest income',
+ 'Account Name' => 'Interest income',
+ 'Amount With Sym' => '$0.00',
+ 'Amount Num' =>
+ [
+ '' => '0.00',
+ ],
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '0.00',
+ ]
+ ];
+
+ $output = $this->converter->convert($input, self::COMPANY1);
+
+ self::assertSame([
+ [
+ 'txnDate' => '2018-09-28',
+ 'refNumber' => NULL,
+ 'currency' => 'Hong Kong Dollar',
+ 'exchangeRate' => NULL,
+ 'creditAccount' => 'Interest Income',
+ 'creditMemo' => 'CREDIT INTEREST',
+ 'creditAmount' => '0.01',
+ 'debitAccount' => 'HDFC HKD Savings',
+ 'debitMemo' => 'CREDIT INTEREST',
+ 'debitAmount' => '0.01',
+ ]
+ ], $output);
+ }
+
+ public function testConvertIncomeUsdToHkd(): void
+ {
+ $input = [
+ [
+ 'Date' => '01/01/20',
+ 'Transaction ID' => '17fdb9fe2ec0d4a84533e96094875971',
+ 'Number' => '',
+ 'Description' => 'Withdrawal Conversion from: $173.19 USD Conversion to: $1,307.53 HKD Exchange rate: 7.5496846',
+ 'Notes' => '',
+ 'Commodity/Currency' => 'CURRENCY::USD',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Assets:Current Assets:HDFC Savings',
+ 'Account Name' => 'HDFC Savings',
+ 'Amount With Sym' => 'HK$1,307.53',
+ 'Amount Num' =>
+ [
+ '' => '1,307.53',
+ ],
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '17319/130753',
+ ],
+ [
+ 'Date' => '',
+ 'Transaction ID' => '',
+ 'Number' => '',
+ 'Description' => '',
+ 'Notes' => '',
+ 'Commodity/Currency' => '',
+ 'Void Reason' => '',
+ 'Action' => '',
+ 'Memo' => '',
+ 'Full Account Name' => 'Assets:Current Assets:Paypal USD',
+ 'Account Name' => 'Paypal USD',
+ 'Amount With Sym' => '-$173.19',
+ 'Amount Num' =>
+ [
+ '' => '-173.19',
+ ],
+ 'Reconcile' => 'n',
+ 'Reconcile Date' => '',
+ 'Rate/Price' => '1',
+ ]
+ ];
+
+ $output = $this->converter->convert($input, self::COMPANY1);
+
+ self::assertSame([
+ [
+ 'txnDate' => '2020-01-01',
+ 'refNumber' => NULL,
+ 'currency' => 'Hong Kong Dollar',
+ 'exchangeRate' => NULL,
+ 'creditAccount' => 'Uncategorized Income',
+ 'creditMemo' => 'Withdrawal Conversion from: $173.19 USD Conversion to: $1,307.53 HKD Exchange rate: 7.5496846',
+ 'creditAmount' => '1,307.53',
+ 'debitAccount' => 'HDFC HKD Savings',
+ 'debitMemo' => 'Withdrawal Conversion from: $173.19 USD Conversion to: $1,307.53 HKD Exchange rate: 7.5496846',
+ 'debitAmount' => '1,307.53',
+ ]
+ ], $output);
+ }
+}
diff --git a/tests/Unit/fixtures/accounts_mapping.json b/tests/Unit/fixtures/accounts_mapping.json
new file mode 100644
index 0000000..0fd91bc
--- /dev/null
+++ b/tests/Unit/fixtures/accounts_mapping.json
@@ -0,0 +1,12 @@
+{
+ "acme1": {
+ "Assets:Current Assets:HDFC Savings": "HDFC HKD Savings",
+ "Income:Interest income": "Interest Income",
+ "Expense:Bank Charges HKD": "Bank Service Charges"
+ },
+ "acme2": {
+ "Assets:Current Assets:Citibank EUR": "Citibank EUR",
+ "Assets:Current Assets:Citibank USD": "Citibank USD",
+ "Expense:Bank Charges USD": "Bank Service Charges"
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..469dcce
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,11 @@
+bootEnv(dirname(__DIR__).'/.env');
+}
diff --git a/translations/.gitignore b/translations/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml
new file mode 100644
index 0000000..641b3cc
--- /dev/null
+++ b/translations/messages.en.yaml
@@ -0,0 +1,4 @@
+# wizard buttons
+import_wizard_step.upload: Upload
+import_wizard_step.mapping: Mapping
+import_wizard_step.confirmation: Confirmation