diff --git a/app/Http/Requests/Location/StoreLocationRequest.php b/app/Http/Requests/Location/StoreLocationRequest.php index a1e51e0905..d0e6e21cd3 100644 --- a/app/Http/Requests/Location/StoreLocationRequest.php +++ b/app/Http/Requests/Location/StoreLocationRequest.php @@ -39,7 +39,8 @@ class StoreLocationRequest extends Request $rules = []; $rules['name'] = 'required|unique:expense_categories,name,null,null,company_id,'.$user->companyId(); - + $rules['country_id'] = 'integer|exists:countries,id'; + return $this->globalRules($rules); } @@ -53,6 +54,7 @@ class StoreLocationRequest extends Request $input['color'] = ''; } + $this->replace($input); } } diff --git a/app/Http/Requests/Location/UpdateLocationRequest.php b/app/Http/Requests/Location/UpdateLocationRequest.php index b33648ac0d..6c5274ce1d 100644 --- a/app/Http/Requests/Location/UpdateLocationRequest.php +++ b/app/Http/Requests/Location/UpdateLocationRequest.php @@ -45,6 +45,8 @@ class UpdateLocationRequest extends Request $rules['name'] = Rule::unique('locations')->where('company_id', $user->company()->id)->ignore($this->location->id); } + $rules['country_id'] = 'integer|exists:countries,id'; + return $rules; } diff --git a/app/Models/Location.php b/app/Models/Location.php index a41a23eb6d..797434a172 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -49,6 +49,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property string|null $custom_value3 * @property string|null $custom_value4 * @property bool $is_deleted + * @property bool $is_shipping_location * @property int|null $created_at * @property int|null $updated_at * @property int|null $deleted_at @@ -87,6 +88,7 @@ class Location extends BaseModel 'custom_value3', 'custom_value4', 'is_deleted', + 'is_shipping_location', ]; diff --git a/app/Models/Presenters/ClientPresenter.php b/app/Models/Presenters/ClientPresenter.php index caccc286d1..d80074170c 100644 --- a/app/Models/Presenters/ClientPresenter.php +++ b/app/Models/Presenters/ClientPresenter.php @@ -158,29 +158,6 @@ class ClientPresenter extends EntityPresenter return $str; } - // public function getCityState() - // { - // $settings = $this->entity->getMergedSettings(); - - // $country = false; - - // if ($settings->country_id) { - // $country = Country::find($settings->country_id); - // } - - // $swap = $country && $country->swap_postal_code; - - // $city = e($settings->city ?: ''); - // $state = e($settings->state ?: ''); - // $postalCode = e($settings->postal_code ?: ''); - - // if ($city || $state || $postalCode) { - // return $this->cityStateZip($city, $state, $postalCode, $swap); - // } else { - // return false; - // } - // } - public function getCityState() { $client = $this->entity; diff --git a/app/Services/Credit/CreditService.php b/app/Services/Credit/CreditService.php index 70509ef650..94cf18f6b9 100644 --- a/app/Services/Credit/CreditService.php +++ b/app/Services/Credit/CreditService.php @@ -11,15 +11,16 @@ namespace App\Services\Credit; -use App\Factory\PaymentFactory; -use App\Jobs\EDocument\CreateEDocument; +use App\Utils\Ninja; use App\Models\Credit; use App\Models\Payment; use App\Models\PaymentType; -use App\Repositories\CreditRepository; -use App\Repositories\PaymentRepository; -use App\Utils\Ninja; +use App\Factory\PaymentFactory; use App\Utils\Traits\MakesHash; +use App\Repositories\CreditRepository; +use App\Services\Invoice\LocationData; +use App\Jobs\EDocument\CreateEDocument; +use App\Repositories\PaymentRepository; use Illuminate\Support\Facades\Storage; class CreditService @@ -33,6 +34,11 @@ class CreditService $this->credit = $credit; } + public function location(): array + { + return (new LocationData($this->credit))->run(); + } + public function getCreditPdf($invitation) { return (new GetCreditPdf($invitation))->run(); diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index 543a79828d..b1a405b7d0 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -11,21 +11,22 @@ namespace App\Services\Invoice; -use App\Events\Invoice\InvoiceWasArchived; -use App\Jobs\EDocument\CreateEDocument; -use App\Jobs\Entity\CreateRawPdf; -use App\Jobs\Inventory\AdjustProductInventory; -use App\Libraries\Currency\Conversion\CurrencyApi; -use App\Models\CompanyGateway; +use App\Models\Task; +use App\Utils\Ninja; use App\Models\Expense; use App\Models\Invoice; use App\Models\Payment; use App\Models\Subscription; -use App\Models\Task; -use App\Utils\Ninja; -use App\Utils\Traits\MakesHash; +use App\Models\CompanyGateway; use Illuminate\Support\Carbon; +use App\Utils\Traits\MakesHash; +use App\Jobs\Entity\CreateRawPdf; +use App\Services\Invoice\LocationData; +use App\Jobs\EDocument\CreateEDocument; use Illuminate\Support\Facades\Storage; +use App\Events\Invoice\InvoiceWasArchived; +use App\Jobs\Inventory\AdjustProductInventory; +use App\Libraries\Currency\Conversion\CurrencyApi; class InvoiceService { @@ -617,6 +618,11 @@ class InvoiceService return $this; } + public function location(): array + { + return (new LocationData($this->invoice))->run(); + } + public function workFlow() { if ($this->invoice->status_id == Invoice::STATUS_PAID && $this->invoice->client->getSetting('auto_archive_invoice')) { diff --git a/app/Services/Invoice/LocationData.php b/app/Services/Invoice/LocationData.php new file mode 100644 index 0000000000..97337e288a --- /dev/null +++ b/app/Services/Invoice/LocationData.php @@ -0,0 +1,302 @@ +setLocations(); + } + + private function setLocations(): self + { + if (!$this->entity->location) { + $this->businessLocation = null; + $this->shippingLocation = null; + } + elseif($this->entity->location->is_shipping_location) { + $this->shippingLocation = $this->entity->location; + $this->businessLocation = null; + } else { + $this->businessLocation = $this->entity->location; + $this->shippingLocation = null; + } + + return $this; + } + + public function run(): array + { + return [ + // Business Address (from business location or client default) + 'address' => $this->getBusinessAddress(), + 'address1' => $this->getBusinessAddress1(), + 'address2' => $this->getBusinessAddress2(), + 'city' => $this->getBusinessCity(), + 'state' => $this->getBusinessState(), + 'postal_code' => $this->getBusinessPostalCode(), + 'country' => $this->getBusinessCountry(), + 'country_name' => $this->getBusinessCountryName(), + 'country_code' => $this->getBusinessCountryCode(), + + // Shipping Address (from shipping location or client default) + 'shipping_address' => $this->getShippingAddress(), + 'shipping_address1' => $this->getShippingAddress1(), + 'shipping_address2' => $this->getShippingAddress2(), + 'shipping_city' => $this->getShippingCity(), + 'shipping_state' => $this->getShippingState(), + 'shipping_postal_code' => $this->getShippingPostalCode(), + 'shipping_country' => $this->getShippingCountry(), + 'shipping_country_name' => $this->getShippingCountryName(), + 'shipping_country_code' => $this->getShippingCountryCode(), + 'shipping_exists' => strlen($this->getShippingAddress1()) > 0, + ]; + } + + private function getBusinessCountry(): ?Country + { + + if ($this->businessLocation) { + return $this->businessLocation->country ?? $this->entity->company->country(); + } + + return $this->entity->client->country ?? $this->entity->company->country(); + + } + + private function getShippingCountry(): ?Country + { + if ($this->shippingLocation) { + return $this->shippingLocation->country ?? $this->entity->company->country(); + } + + return $this->entity->client->shipping_country ?? $this->entity->company->country(); + + } + + public function getCityState() + { + $country = $this->getBusinessCountry(); + + $swap = $country && $country->swap_postal_code; + + $city = e($this->getBusinessCity() ?: ''); + $state = e($this->getBusinessState() ?: ''); + $postalCode = e($this->getBusinessPostalCode() ?: ''); + + if ($city || $state || $postalCode) { + return $this->entity->present()->cityStateZip($city, $state, $postalCode, $swap); + } else { + return false; + } + } + + private function getBusinessAddress(): ?string + { + $str = ' '; + + if ($address1 = $this->getBusinessAddress1()) { + $str .= e($address1).'
'; + } + if ($address2 = $this->getBusinessAddress2()) { + $str .= e($address2).'
'; + } + if ($cityState = $this->getCityState()) { + $str .= e($cityState).'
'; + } + if ($country = $this->getBusinessCountryName()) { + $str .= e($country).'
'; + } + + return $str; + + } + + private function getShippingCityState(): ?string + { + $country = $this->getShippingCountry(); + + $swap = $country && $country->swap_postal_code; + + $city = e($this->getShippingCity() ?: ''); + $state = e($this->getShippingState() ?: ''); + $postalCode = e($this->getShippingPostalCode() ?: ''); + + if ($city || $state || $postalCode) { + return $this->entity->present()->cityStateZip($city, $state, $postalCode, $swap); + } else { + return false; + } + } + + private function getShippingAddress(): ?string + { + + $str = ' '; + + if ($address1 = $this->getShippingAddress1()) { + $str .= e($address1).'
'; + } + if ($address2 = $this->getShippingAddress2()) { + $str .= e($address2).'
'; + } + if ($cityState = $this->getShippingCityState()) { + $str .= e($cityState).'
'; + } + if ($country = $this->getShippingCountryName()) { + $str .= e($country).'
'; + } + + return $str; + + + } + + private function getBusinessAddress1(): ?string + { + if ($this->businessLocation) { + return $this->businessLocation->address1 ?? ''; + } + + return $this->entity->client->address1 ?? ''; + } + + private function getBusinessAddress2(): ?string + { + if ($this->businessLocation) { + return $this->businessLocation->address2 ?? ''; + } + + return $this->entity->client->address2 ?? ''; + } + + private function getBusinessCity(): ?string + { + if ($this->businessLocation) { + return $this->businessLocation->city ?? ''; + } + + return $this->entity->client->city ?? ''; + } + + private function getBusinessState(): ?string + { + if ($this->businessLocation) { + return $this->businessLocation->state ?? ''; + } + + return $this->entity->client->state ?? ''; + } + + private function getBusinessPostalCode(): ?string + { + if ($this->businessLocation) { + return $this->businessLocation->postal_code ?? ''; + } + + return $this->entity->client->postal_code ?? ''; + } + + private function getBusinessCountryName(): ?string + { + if ($this->businessLocation) { + return $this->businessLocation->country->name; + } + + return $this->entity->client->country->name; + } + + private function getBusinessCountryCode(): ?string + { + if ($this->businessLocation) { + return $this->businessLocation->country->iso_3166_2; + } + + return $this->entity->client->country->iso_3166_2; + } + + private function getShippingAddress1(): ?string + { + if ($this->shippingLocation) { + return $this->shippingLocation->address1 ?? ''; + } + + return $this->entity->client->shipping_address1 ?? ''; + } + + private function getShippingAddress2(): ?string + { + if ($this->shippingLocation) { + return $this->shippingLocation->address2 ?? ''; + } + + return $this->entity->client->shipping_address2 ?? ''; + } + + private function getShippingCity(): ?string + { + if ($this->shippingLocation) { + return $this->shippingLocation->city ?? ''; + } + + return $this->entity->client->shipping_city ?? ''; + } + + private function getShippingState(): ?string + { + if ($this->shippingLocation) { + return $this->shippingLocation->state ?? ''; + } + + return $this->entity->client->shipping_state ?? ''; + } + + private function getShippingPostalCode(): ?string + { + if ($this->shippingLocation) { + return $this->shippingLocation->postal_code ?? ''; + } + + return $this->entity->client->shipping_postal_code ?? ''; + } + + private function getShippingCountryName(): ?string + { + if ($this->shippingLocation && $this->shippingLocation->country) { + return $this->shippingLocation->country->name; + } + + return $this->entity->client->shipping_country->name ?? $this->entity->company->country()->name; + } + + private function getShippingCountryCode(): ?string + { + if ($this->shippingLocation && $this->shippingLocation->country) { + return $this->shippingLocation->country->iso_3166_2; + } + + return $this->entity->client->shipping_country->iso_3166_2 ?? $this->entity->company->country()->iso_3166_2; + } +} diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index 1b484c3b5a..2fd45ba08a 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -11,15 +11,16 @@ namespace App\Services\Quote; -use App\Events\Quote\QuoteWasApproved; -use App\Exceptions\QuoteConversion; -use App\Jobs\EDocument\CreateEDocument; -use App\Models\Project; -use App\Models\Quote; -use App\Repositories\QuoteRepository; -use App\Services\Quote\UpdateReminder; use App\Utils\Ninja; +use App\Models\Quote; +use App\Models\Project; use App\Utils\Traits\MakesHash; +use App\Exceptions\QuoteConversion; +use App\Repositories\QuoteRepository; +use App\Events\Quote\QuoteWasApproved; +use App\Services\Invoice\LocationData; +use App\Services\Quote\UpdateReminder; +use App\Jobs\EDocument\CreateEDocument; use Illuminate\Support\Facades\Storage; class QuoteService @@ -35,6 +36,11 @@ class QuoteService $this->quote = $quote; } + public function location(): array + { + return (new LocationData($this->quote))->run(); + } + public function createInvitations() { $this->quote = (new CreateInvitations($this->quote))->run(); diff --git a/app/Services/Recurring/RecurringService.php b/app/Services/Recurring/RecurringService.php index a40c327464..3a87dd5f26 100644 --- a/app/Services/Recurring/RecurringService.php +++ b/app/Services/Recurring/RecurringService.php @@ -18,6 +18,7 @@ use Illuminate\Support\Carbon; use App\Utils\Traits\MakesHash; use App\Models\RecurringExpense; use App\Models\RecurringInvoice; +use App\Services\Invoice\LocationData; use Illuminate\Support\Facades\Storage; use App\Jobs\RecurringInvoice\SendRecurring; @@ -177,6 +178,11 @@ class RecurringService return $this; } + + public function location(): array + { + return (new LocationData($this->recurring_entity))->run(); + } public function save() { diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index bc8504fdf8..85c8aee26f 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -128,16 +128,17 @@ class HtmlEngine App::setLocale($this->contact->preferredLocale()); $t->replace(Ninja::transformTranslations($this->settings)); + $locationData = $this->entity->service()->location(); + $data = []; $data['$date_client_now'] = ['value' => now()->setTimezone($this->client->timezone()->name)->addSeconds($this->client->utc_offset())->format($this->client->date_format()), 'label' => '']; $data['$date_company_now'] = ['value' => now()->setTimezone($this->company->timezone()->name)->addSeconds($this->company->utc_offset())->format($this->company->date_format()), 'label' => '']; $data['$global_margin'] = ['value' => '6.35mm', 'label' => '']; $data['$company_logo_size'] = ['value' => $this->resolveCompanyLogoSize(), 'label' => '']; - $data['$show_shipping_address'] = ['value' => strlen($this->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 'flex' : 'none', 'label' => '']; - $data['$show_shipping_address_block'] = ['value' => strlen($this->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 'block' : 'none', 'label' => '']; - // $data['$show_shipping_address_visibility'] = ['value' => $this->settings?->show_shipping_address ? 'visible' : 'hidden', 'label' => '']; - $data['$show_shipping_address_visibility'] = ['value' => strlen($this->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 1 : 0, 'label' => '']; + $data['$show_shipping_address'] = ['value' => $locationData['shipping_exists'] && $this->settings->show_shipping_address ? 'flex' : 'none', 'label' => '']; + $data['$show_shipping_address_block'] = ['value' => $locationData['shipping_exists'] && $this->settings->show_shipping_address ? 'block' : 'none', 'label' => '']; + $data['$show_shipping_address_visibility'] = ['value' => $locationData['shipping_exists'] && $this->settings->show_shipping_address ? 1 : 0, 'label' => '']; $data['$order_number'] = ['value' => '', 'label' => ctrans('texts.order_number')]; $data['$tax'] = ['value' => '', 'label' => ctrans('texts.tax')]; @@ -457,15 +458,15 @@ class HtmlEngine $data['$client.custom2'] = &$data['$client2']; $data['$client.custom3'] = &$data['$client3']; $data['$client.custom4'] = &$data['$client4']; - $data['$address1'] = ['value' => $this->client->address1 ?: ' ', 'label' => ctrans('texts.address1')]; - $data['$address2'] = ['value' => $this->client->address2 ?: ' ', 'label' => ctrans('texts.address2')]; + $data['$address1'] = ['value' => $locationData['address1'] ?: ' ', 'label' => ctrans('texts.address1')]; + $data['$address2'] = ['value' => $locationData['address2'] ?: ' ', 'label' => ctrans('texts.address2')]; $data['$id_number'] = ['value' => $this->client->id_number ?: ' ', 'label' => ctrans('texts.id_number')]; $data['$client.number'] = ['value' => $this->client->number ?: ' ', 'label' => ctrans('texts.number')]; $data['$vat_number'] = ['value' => $this->client->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')]; $data['$website'] = ['value' => $this->client->present()->website() ?: ' ', 'label' => ctrans('texts.website')]; $data['$phone'] = ['value' => $this->client->present()->phone() ?: ' ', 'label' => ctrans('texts.phone')]; - $data['$country'] = ['value' => isset($this->client->country->name) ? ctrans('texts.country_' . $this->client->country->name) : '', 'label' => ctrans('texts.country')]; - $data['$country_2'] = ['value' => isset($this->client->country) ? $this->client->country->iso_3166_2 : '', 'label' => ctrans('texts.country')]; + $data['$country'] = ['value' => ctrans('texts.country_' . $locationData['country_name']) , 'label' => ctrans('texts.country')]; + $data['$country_2'] = ['value' => $locationData['country_code'] , 'label' => ctrans('texts.country')]; $data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')]; if (str_contains($data['$email']['value'], 'example.com')) { @@ -478,21 +479,21 @@ class HtmlEngine $data['$client.address1'] = &$data['$address1']; $data['$client.address2'] = &$data['$address2']; - $data['$client_address'] = ['value' => $this->client->present()->address() ?: ' ', 'label' => ctrans('texts.address')]; + $data['$client_address'] = ['value' => $locationData['address'], 'label' => ctrans('texts.address')]; $data['$client.address'] = &$data['$client_address']; - $data['$client.postal_code'] = ['value' => $this->client->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')]; + $data['$client.postal_code'] = ['value' => $locationData['postal_code'] ?: ' ', 'label' => ctrans('texts.postal_code')]; $data['$client.public_notes'] = ['value' => $this->client->public_notes ?: ' ', 'label' => ctrans('texts.notes')]; - $data['$client.city'] = ['value' => $this->client->city ?: ' ', 'label' => ctrans('texts.city')]; - $data['$client.state'] = ['value' => $this->client->state ?: ' ', 'label' => ctrans('texts.state')]; + $data['$client.city'] = ['value' => $locationData['city'] ?: ' ', 'label' => ctrans('texts.city')]; + $data['$client.state'] = ['value' => $locationData['state'] ?: ' ', 'label' => ctrans('texts.state')]; $data['$client.id_number'] = &$data['$id_number']; $data['$client.vat_number'] = &$data['$vat_number']; $data['$client.website'] = &$data['$website']; $data['$client.phone'] = &$data['$phone']; - $data['$city_state_postal'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; + $data['$city_state_postal'] = ['value' => $this->entity->present()->cityStateZip($locationData['city'], $locationData['state'], $locationData['postal_code'], false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; $data['$client.city_state_postal'] = &$data['$city_state_postal']; - $data['$postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; + $data['$postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($locationData['city'], $locationData['state'], $locationData['postal_code'], true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; $data['$client.postal_city_state'] = &$data['$postal_city_state']; - $data['$postal_city'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, null, $this->client->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city')]; + $data['$postal_city'] = ['value' => $this->entity->present()->cityStateZip($locationData['city'], null, $locationData['postal_code'], true) ?: ' ', 'label' => ctrans('texts.postal_city')]; $data['$client.postal_city'] = &$data['$postal_city']; $data['$client.country'] = &$data['$country']; $data['$client.email'] = &$data['$email']; @@ -505,25 +506,24 @@ class HtmlEngine $data['$client.billing_postal_code'] = &$data['$client.postal_code']; $data['$client.billing_country'] = &$data['$client.country']; - $data['$client.shipping_address'] = ['value' => $this->client->present()->shipping_address() ?: ' ', 'label' => ctrans('texts.shipping_address')]; - $data['$client.shipping_address1'] = ['value' => $this->client->shipping_address1 ?: ' ', 'label' => ctrans('texts.shipping_address1')]; - $data['$client.shipping_address2'] = ['value' => $this->client->shipping_address2 ?: ' ', 'label' => ctrans('texts.shipping_address2')]; - $data['$client.shipping_city'] = ['value' => $this->client->shipping_city ?: ' ', 'label' => ctrans('texts.shipping_city')]; - $data['$client.shipping_state'] = ['value' => $this->client->shipping_state ?: ' ', 'label' => ctrans('texts.shipping_state')]; - $data['$client.shipping_postal_code'] = ['value' => $this->client->shipping_postal_code ?: ' ', 'label' => ctrans('texts.shipping_postal_code')]; - $data['$client.shipping_country'] = ['value' => isset($this->client->shipping_country->name) ? ctrans('texts.country_' . $this->client->shipping_country->name) : '', 'label' => ctrans('texts.shipping_country')]; - $data['$shipping_postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($this->client->shipping_city, $this->client->shipping_state, $this->client->shipping_postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; + $data['$client.shipping_address'] = ['value' => $locationData['shipping_address'], 'label' => ctrans('texts.shipping_address')]; + $data['$client.shipping_address1'] = ['value' => $locationData['shipping_address1'], 'label' => ctrans('texts.shipping_address1')]; + $data['$client.shipping_address2'] = ['value' => $locationData['shipping_address2'], 'label' => ctrans('texts.shipping_address2')]; + $data['$client.shipping_city'] = ['value' => $locationData['shipping_city'], 'label' => ctrans('texts.shipping_city')]; + $data['$client.shipping_state'] = ['value' => $locationData['shipping_state'], 'label' => ctrans('texts.shipping_state')]; + $data['$client.shipping_postal_code'] = ['value' => $locationData['shipping_postal_code'], 'label' => ctrans('texts.shipping_postal_code')]; + $data['$client.shipping_country'] = ['value' => ctrans('texts.country_' . $locationData['shipping_country_name']), 'label' => ctrans('texts.shipping_country')]; + $data['$shipping_postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($locationData['shipping_city'], $locationData['shipping_state'], $locationData['shipping_postal_code'], true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; $data['$client.shipping_postal_city_state'] = &$data['$shipping_postal_city_state']; - $data['$shipping_postal_city'] = ['value' => $this->entity->present()->cityStateZip($this->client->shipping_city, null, $this->client->shipping_postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city')]; + $data['$shipping_postal_city'] = ['value' => $this->entity->present()->cityStateZip($locationData['shipping_city'], null, $locationData['shipping_postal_code'], true) ?: ' ', 'label' => ctrans('texts.postal_city')]; $data['$client.shipping_postal_city'] = &$data['$shipping_postal_city']; - $data['$shipping_city_state_postal'] = ['value' => $this->entity->present()->cityStateZip($this->client->shipping_city, $this->client->shipping_state, $this->client->shipping_postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; + $data['$shipping_city_state_postal'] = ['value' => $this->entity->present()->cityStateZip($locationData['shipping_city'], $locationData['shipping_state'], $locationData['shipping_postal_code'], false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; $data['$client.shipping_city_state_postal'] = &$data['$shipping_city_state_postal']; $data['$client.currency'] = ['value' => $this->client->currency()->code, 'label' => '']; $data['$client.lang_2'] = ['value' => optional($this->client->language())->locale, 'label' => '']; - $data['$client.balance'] = ['value' => Number::formatMoney($this->client->balance, $this->client), 'label' => ctrans('texts.account_balance')]; $data['$client.payment_balance'] = ['value' => Number::formatMoney($this->client->payment_balance, $this->client), 'label' => ctrans('texts.payment_balance_on_file')]; $data['$client_balance'] = ['value' => Number::formatMoney($this->client->balance, $this->client), 'label' => ctrans('texts.account_balance')]; @@ -970,30 +970,7 @@ class HtmlEngine return $country ? $country->iso_3166_2 : ' '; } - /** - * Due to the way we are compiling the blade template we - * have no ability to iterate, so in the case - * of line taxes where there are multiple rows, - * we use this function to format a section of rows. - * - * @return string a collection of rows with line item - * aggregate data - */ - // private function makeLineTaxes(): string - // { - // $tax_map = $this->entity_calc->getTaxMap(); - - // $data = ''; - - // foreach ($tax_map as $tax) { - // $data .= ''; - // $data .= ''.$tax['name'].''; - // $data .= ''.Number::formatMoney($tax['total'], $this->client).''; - // } - - // return $data; - // } - + private function lineTaxValues(): string { $tax_map = $this->entity_calc->getTaxMap(); @@ -1007,31 +984,6 @@ class HtmlEngine return $data; } - // private function makeTotalTaxes(): string - // { - // $data = ''; - - // if (! $this->entity_calc->getTotalTaxMap()) { - // return $data; - // } - - // foreach ($this->entity_calc->getTotalTaxMap() as $tax) { - // $data .= ''; - // $data .= ''; - // $data .= ''.$tax['name'].''; - // $data .= ''.Number::formatMoney($tax['total'], $this->client).''; - // } - - // return $data; - // } - - // private function parseLabelsAndValues($labels, $values, $section): string - // { - // $section = strtr($section, $labels); - - // return strtr($section, $values); - // } - /* | Ensures the URL doesn't have duplicated trailing slash */ @@ -1041,104 +993,6 @@ class HtmlEngine return config('ninja.app_url'); } - /** - * Builds CSS to assist with the generation - * of Repeating headers and footers on the PDF. - * @return string The css string - */ - // private function generateCustomCSS(): string - // { - // $header_and_footer = ' - // .header, .header-space { - // height: 160px; - // } - - // .footer, .footer-space { - // height: 160px; - // } - - // .footer { - // position: fixed; - // bottom: 0; - // width: 100%; - // } - - // .header { - // position: fixed; - // top: 0mm; - // width: 100%; - // } - - // @media print { - // thead {display: table-header-group;} - // tfoot {display: table-footer-group;} - // button {display: none;} - // body {margin: 0;} - // }'; - - // $header = ' - // .header, .header-space { - // height: 160px; - // } - - // .header { - // position: fixed; - // top: 0mm; - // width: 100%; - // } - - // @media print { - // thead {display: table-header-group;} - // button {display: none;} - // body {margin: 0;} - // }'; - - // $footer = ' - - // .footer, .footer-space { - // height: 160px; - // } - - // .footer { - // position: fixed; - // bottom: 0; - // width: 100%; - // } - - // @media print { - // tfoot {display: table-footer-group;} - // button {display: none;} - // body {margin: 0;} - // }'; - // $css = ''; - - // if ($this->settings->all_pages_header && $this->settings->all_pages_footer) { - // $css .= $header_and_footer; - // } elseif ($this->settings->all_pages_header && ! $this->settings->all_pages_footer) { - // $css .= $header; - // } elseif (! $this->settings->all_pages_header && $this->settings->all_pages_footer) { - // $css .= $footer; - // } - - // $css .= ' - // .page { - // page-break-after: always; - // } - - // @page { - // margin: 0mm - // } - - // html { - // '; - - // $css .= 'font-size:'.$this->settings->font_size.'px;'; - - // $css .= '}'; - - // return $css; - // } - /** * Generate markup for HTML images on entity. * @@ -1216,15 +1070,5 @@ class HtmlEngine '; - - // return ' - // - // - // - // - //
- // '. $text .' - //
- // '; } } diff --git a/database/migrations/2025_02_20_224129_entity_location_schema.php b/database/migrations/2025_02_20_224129_entity_location_schema.php index 0a91d03b4e..41e22944ed 100644 --- a/database/migrations/2025_02_20_224129_entity_location_schema.php +++ b/database/migrations/2025_02_20_224129_entity_location_schema.php @@ -25,6 +25,7 @@ return new class extends Migration $table->string('city')->nullable(); $table->string('state')->nullable(); $table->string('postal_code')->nullable(); + $table->boolean('is_shipping_location')->default(false); $table->boolean('is_deleted')->default(false); $table->unsignedInteger('country_id')->nullable(); diff --git a/tests/Feature/LocationApiTest.php b/tests/Feature/LocationApiTest.php index 1d699e831a..b2f8a2a1e3 100644 --- a/tests/Feature/LocationApiTest.php +++ b/tests/Feature/LocationApiTest.php @@ -2,11 +2,15 @@ namespace Tests\Feature; -use App\Models\Location; -use Illuminate\Foundation\Testing\DatabaseTransactions; -use Illuminate\Support\Facades\Session; -use Tests\MockAccountData; use Tests\TestCase; +use App\Models\Client; +use App\Models\Invoice; +use App\Models\Location; +use App\Utils\HtmlEngine; +use Tests\MockAccountData; +use App\Models\ClientContact; +use Illuminate\Support\Facades\Session; +use Illuminate\Foundation\Testing\DatabaseTransactions; class LocationApiTest extends TestCase { @@ -21,6 +25,267 @@ class LocationApiTest extends TestCase Session::start(); } + + public function testResolvingNormalShippingLocationData() + { + $client = Client::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'address1' => '123 Test St', + 'address2' => 'Suite 100', + 'city' => 'Test City', + 'state' => 'TS', + 'postal_code' => '12345', + 'country_id' => '840', // USA + 'shipping_address1' => 'Shipping Address 1', + 'shipping_address2' => 'Shipping Address 2', + 'shipping_city' => 'Shipping City', + 'shipping_state' => 'Shipping State', + 'shipping_postal_code' => 'Shipping Postal Code', + 'shipping_country_id' => '4', + ]); + + $contact = ClientContact::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'first_name' => 'Test', + 'last_name' => 'Contact', + 'email' => 'test@test.com', + 'send_email' => true, + ]); + + $location = Location::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'address1' => 'Location Address 1', + 'address2' => 'Location Address 2', + 'city' => 'Location City', + 'state' => 'Location State', + 'postal_code' => 'Location Postal Code', + 'country_id' => '4', + 'is_shipping_location' => false, + ]); + + $invoice = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'location_id' => $location->id, + ]); + + $invoice->service()->createInvitations()->markSent()->save(); + + $invitation = $invoice->invitations()->first(); + + $html_engine = new HtmlEngine($invitation); + $html_engine->buildEntityDataArray(); + $data = $html_engine->makeValues(); + + $this->assertEquals('Location Address 1', $data['$client.address1']); + $this->assertEquals('Location Address 2', $data['$client.address2']); + $this->assertEquals('Location City', $data['$client.city']); + $this->assertEquals('Location State', $data['$client.state']); + $this->assertEquals('Location Postal Code', $data['$client.postal_code']); + $this->assertEquals('Afghanistan', $data['$client.country']); + + $this->assertEquals('Shipping Address 1', $data['$client.shipping_address1']); + $this->assertEquals('Shipping Address 2', $data['$client.shipping_address2']); + $this->assertEquals('Shipping City', $data['$client.shipping_city']); + $this->assertEquals('Shipping State', $data['$client.shipping_state']); + $this->assertEquals('Shipping Postal Code', $data['$client.shipping_postal_code']); + $this->assertEquals('Afghanistan', $data['$client.shipping_country']); + + } + + public function testResolvingNormalLocationData() + { + $client = Client::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'address1' => '123 Test St', + 'address2' => 'Suite 100', + 'city' => 'Test City', + 'state' => 'TS', + 'postal_code' => '12345', + 'country_id' => '840', // USA + ]); + + $contact = ClientContact::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'first_name' => 'Test', + 'last_name' => 'Contact', + 'email' => 'test@test.com', + 'send_email' => true, + ]); + + $location = Location::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'address1' => 'Location Address 1', + 'address2' => 'Location Address 2', + 'city' => 'Location City', + 'state' => 'Location State', + 'postal_code' => 'Location Postal Code', + 'country_id' => '4', + 'is_shipping_location' => true, + ]); + + $invoice = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'location_id' => $location->id, + ]); + + $invoice->service()->createInvitations()->markSent()->save(); + + $invitation = $invoice->invitations()->first(); + + $html_engine = new HtmlEngine($invitation); + $html_engine->buildEntityDataArray(); + $data = $html_engine->makeValues(); + + $this->assertEquals('123 Test St', $data['$client.address1']); + $this->assertEquals('Suite 100', $data['$client.address2']); + $this->assertEquals('Test City', $data['$client.city']); + $this->assertEquals('TS', $data['$client.state']); + $this->assertEquals('12345', $data['$client.postal_code']); + $this->assertEquals('United States', $data['$client.country']); + + $this->assertEquals('Location Address 1', $data['$client.shipping_address1']); + $this->assertEquals('Location Address 2', $data['$client.shipping_address2']); + $this->assertEquals('Location City', $data['$client.shipping_city']); + $this->assertEquals('Location State', $data['$client.shipping_state']); + $this->assertEquals('Location Postal Code', $data['$client.shipping_postal_code']); + $this->assertEquals('Afghanistan', $data['$client.shipping_country']); + + } + + + public function testResolvingShippingLocationData() + { + $client = Client::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'address1' => '123 Test St', + 'address2' => 'Suite 100', + 'city' => 'Test City', + 'state' => 'TS', + 'postal_code' => '12345', + 'country_id' => '840', // USA + ]); + + $contact = ClientContact::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'first_name' => 'Test', + 'last_name' => 'Contact', + 'email' => 'test@test.com', + 'send_email' => true, + ]); + + $location = Location::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'address1' => 'Location Address 1', + 'address2' => 'Location Address 2', + 'city' => 'Location City', + 'state' => 'Location State', + 'postal_code' => 'Location Postal Code', + 'country_id' => '4', + 'is_shipping_location' => true, + ]); + + $invoice = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'location_id' => $location->id, + ]); + + $invoice->service()->createInvitations()->markSent()->save(); + + $invitation = $invoice->invitations()->first(); + + $html_engine = new HtmlEngine($invitation); + $html_engine->buildEntityDataArray(); + $data = $html_engine->makeValues(); + + $this->assertEquals('Location Address 1', $data['$client.shipping_address1']); + $this->assertEquals('Location Address 2', $data['$client.shipping_address2']); + $this->assertEquals('Location City', $data['$client.shipping_city']); + $this->assertEquals('Location State', $data['$client.shipping_state']); + $this->assertEquals('Location Postal Code', $data['$client.shipping_postal_code']); + $this->assertEquals('Afghanistan', $data['$client.shipping_country']); + + } + + public function testResolvingBusinessLocationData() + { + $client = Client::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'address1' => '123 Test St', + 'address2' => 'Suite 100', + 'city' => 'Test City', + 'state' => 'TS', + 'postal_code' => '12345', + 'country_id' => '840', // USA + ]); + + $contact = ClientContact::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'first_name' => 'Test', + 'last_name' => 'Contact', + 'email' => 'test@test.com', + 'send_email' => true, + ]); + + $location = Location::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'address1' => 'Location Address 1', + 'address2' => 'Location Address 2', + 'city' => 'Location City', + 'state' => 'Location State', + 'postal_code' => 'Location Postal Code', + 'country_id' => '4', + ]); + + $invoice = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'location_id' => $location->id, + ]); + + $invoice->service()->createInvitations()->markSent()->save(); + + $invitation = $invoice->invitations()->first(); + + $html_engine = new HtmlEngine($invitation); + $html_engine->buildEntityDataArray(); + $data = $html_engine->makeValues(); + + $this->assertEquals('Location Address 1', $data['$client.address1']); + $this->assertEquals('Location Address 2', $data['$client.address2']); + $this->assertEquals('Location City', $data['$client.city']); + $this->assertEquals('Location State', $data['$client.state']); + $this->assertEquals('Location Postal Code', $data['$client.postal_code']); + $this->assertEquals('Afghanistan', $data['$client.country']); + + } + public function testLocationPost() { $data = [