mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #9425 from turbo124/v5-develop
Bulk assignment of clients to a group
This commit is contained in:
commit
8326387e8c
@ -64,7 +64,9 @@ class InvoiceItem
|
||||
public $task_id = '';
|
||||
|
||||
public $expense_id = '';
|
||||
|
||||
|
||||
public $unit_code = 'C62';
|
||||
|
||||
public static $casts = [
|
||||
'task_id' => 'string',
|
||||
'expense_id' => 'string',
|
||||
@ -92,5 +94,6 @@ class InvoiceItem
|
||||
'custom_value2' => 'string',
|
||||
'custom_value3' => 'string',
|
||||
'custom_value4' => 'string',
|
||||
'unit_code' => 'string',
|
||||
];
|
||||
}
|
||||
|
@ -250,6 +250,14 @@ class ClientController extends BaseController
|
||||
return response()->json(['message' => $hash_or_response], 200);
|
||||
}
|
||||
|
||||
if($action == 'assign_group' && $user->can('edit', $clients->first())){
|
||||
|
||||
$this->client_repo->assignGroup($clients, $request->group_settings_id);
|
||||
|
||||
return $this->listResponse(Client::query()->withTrashed()->company()->whereIn('id', $request->ids));
|
||||
|
||||
}
|
||||
|
||||
$clients->each(function ($client) use ($action, $user) {
|
||||
if ($user->can('edit', $client)) {
|
||||
$this->client_repo->{$action}($client);
|
||||
|
@ -565,9 +565,11 @@ class CompanyGatewayController extends BaseController
|
||||
|
||||
public function importCustomers(TestCompanyGatewayRequest $request, CompanyGateway $company_gateway)
|
||||
{
|
||||
// $x = Cache::pull("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}");
|
||||
|
||||
//Throttle here
|
||||
// if (Cache::get("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}"))
|
||||
// return response()->json(['message' => ctrans('texts.import_started')], 200);
|
||||
if (Cache::has("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}"))
|
||||
return response()->json(['message' => 'Please wait whilst your previous attempts complete.'], 200);
|
||||
|
||||
dispatch(function () use($company_gateway) {
|
||||
MultiDB::setDb($company_gateway->company->db);
|
||||
|
@ -781,7 +781,7 @@ class CreditController extends BaseController
|
||||
$contact = $invitation->contact;
|
||||
$credit = $invitation->credit;
|
||||
|
||||
$file = $credit->service()->getEInvoice($contact);
|
||||
$file = $credit->service()->getECredit($contact);
|
||||
$file_name = $credit->getFileName("xml");
|
||||
|
||||
$headers = ['Content-Type' => 'application/xml'];
|
||||
|
@ -646,7 +646,6 @@ class PurchaseOrderController extends BaseController
|
||||
echo $file;
|
||||
}, $purchase_order->numberFormatter().".pdf", ['Content-Type' => 'application/pdf']);
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
$this->purchase_order_repository->restore($purchase_order);
|
||||
|
||||
|
@ -35,10 +35,11 @@ class BulkClientRequest extends Request
|
||||
$user = auth()->user();
|
||||
|
||||
return [
|
||||
'action' => 'required|string|in:archive,restore,delete,template',
|
||||
'action' => 'required|string|in:archive,restore,delete,template,assign_group',
|
||||
'ids' => ['required','bail','array',Rule::exists('clients', 'id')->where('company_id', $user->company()->id)],
|
||||
'template' => 'sometimes|string',
|
||||
'template_id' => 'sometimes|string',
|
||||
'group_settings_id' => ['required_if:action,assign_group',Rule::exists('group_settings', 'id')->where('company_id', $user->company()->id)],
|
||||
'send_email' => 'sometimes|bool'
|
||||
];
|
||||
|
||||
@ -52,6 +53,10 @@ class BulkClientRequest extends Request
|
||||
$input['ids'] = $this->transformKeys($input['ids']);
|
||||
}
|
||||
|
||||
if (isset($input['group_settings_id'])) {
|
||||
$input['group_settings_id'] = $this->decodePrimaryKey($input['group_settings_id']);
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -39,62 +39,25 @@ class SubscriptionCron
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
nlog('Subscription Cron');
|
||||
|
||||
Auth::logout();
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$invoices = Invoice::where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->where('is_proforma', 0)
|
||||
->whereDate('due_date', '<=', now()->addDay()->startOfDay())
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('subscription_id')
|
||||
->cursor();
|
||||
|
||||
$invoices->each(function (Invoice $invoice) {
|
||||
$subscription = $invoice->subscription;
|
||||
nlog('Subscription Cron '. now()->toDateTimeString());
|
||||
|
||||
$body = [
|
||||
'context' => 'plan_expired',
|
||||
'client' => $invoice->client->hashed_id,
|
||||
'invoice' => $invoice->hashed_id,
|
||||
'subscription' => $subscription->hashed_id,
|
||||
];
|
||||
$this->timezoneAware();
|
||||
|
||||
$this->sendLoad($subscription, $body);
|
||||
//This will send the notification daily.
|
||||
//We'll need to handle this by performing some action on the invoice to either archive it or delete it?
|
||||
});
|
||||
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$invoices = Invoice::where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->where('is_proforma', 0)
|
||||
->whereDate('due_date', '<=', now()->addDay()->startOfDay())
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('subscription_id')
|
||||
->cursor();
|
||||
nlog('Subscription Cron for ' . $db . ' ' . now()->toDateTimeString());
|
||||
|
||||
$invoices->each(function (Invoice $invoice) {
|
||||
$subscription = $invoice->subscription;
|
||||
$this->timezoneAware();
|
||||
|
||||
$body = [
|
||||
'context' => 'plan_expired',
|
||||
'client' => $invoice->client->hashed_id,
|
||||
'invoice' => $invoice->hashed_id,
|
||||
'subscription' => $subscription->hashed_id,
|
||||
];
|
||||
|
||||
$this->sendLoad($subscription, $body);
|
||||
//This will send the notification daily.
|
||||
//We'll need to handle this by performing some action on the invoice to either archive it or delete it?
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,7 +94,7 @@ class SubscriptionCron
|
||||
->where('is_proforma', 0)
|
||||
->whereNotNull('subscription_id')
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->setTimezone($company->timezone()->name)->addDay()->startOfDay())
|
||||
->whereDate('due_date', '<=', now()->addDay()->startOfDay())
|
||||
->cursor()
|
||||
->each(function (Invoice $invoice) {
|
||||
|
||||
|
@ -88,6 +88,7 @@ class CreateRawPdf
|
||||
'quote' => $type = 'product',
|
||||
'credit' => $type = 'product',
|
||||
'recurring_invoice' => $type = 'product',
|
||||
default => $type = 'product',
|
||||
};
|
||||
|
||||
return $type;
|
||||
|
@ -70,9 +70,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
public function backoff()
|
||||
{
|
||||
// return [5, 10, 30, 240];
|
||||
return [rand(5, 10), rand(30, 40), rand(60, 79), rand(160, 400)];
|
||||
|
||||
return [rand(5, 29), rand(30, 59), rand(61, 100), rand(180, 500)];
|
||||
}
|
||||
|
||||
public function handle()
|
||||
@ -182,6 +180,11 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
$this->fail();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
|
||||
if ($this->nmo->entity) {
|
||||
$this->entityEmailFailed($message);
|
||||
}
|
||||
|
||||
$this->cleanUpMailers();
|
||||
|
||||
return;
|
||||
@ -195,6 +198,11 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
$this->fail();
|
||||
$this->logMailError($message, $this->company->clients()->first());
|
||||
|
||||
if ($this->nmo->entity) {
|
||||
$this->entityEmailFailed($message);
|
||||
}
|
||||
|
||||
$this->cleanUpMailers();
|
||||
|
||||
return;
|
||||
@ -203,7 +211,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
//only report once, not on all tries
|
||||
if ($this->attempts() == $this->tries) {
|
||||
/* If the is an entity attached to the message send a failure mailer */
|
||||
/* If there is an entity attached to the message send a failure mailer */
|
||||
if ($this->nmo->entity) {
|
||||
$this->entityEmailFailed($message);
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ class Account extends BaseModel
|
||||
|
||||
public function isPaid(): bool
|
||||
{
|
||||
return Ninja::isNinja() ? ($this->isPaidHostedClient() && !$this->isTrial()) : $this->hasFeature(self::FEATURE_WHITE_LABEL);
|
||||
return Ninja::isNinja() ? $this->isPaidHostedClient() : $this->hasFeature(self::FEATURE_WHITE_LABEL);
|
||||
}
|
||||
|
||||
public function isPremium(): bool
|
||||
|
@ -190,7 +190,7 @@ class Expense extends BaseModel
|
||||
|
||||
public function purchase_order()
|
||||
{
|
||||
return $this->hasOne(PurchaseOrder::class);
|
||||
return $this->hasOne(PurchaseOrder::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function translate_entity()
|
||||
|
@ -99,6 +99,64 @@ class Product extends BaseModel
|
||||
'tax_id',
|
||||
];
|
||||
|
||||
public array $ubl_tax_map = [
|
||||
self::PRODUCT_TYPE_REVERSE_TAX => 'AE', // VAT_REVERSE_CHARGE =
|
||||
self::PRODUCT_TYPE_EXEMPT => 'E', // EXEMPT_FROM_TAX =
|
||||
self::PRODUCT_TYPE_PHYSICAL => 'S', // STANDARD_RATE =
|
||||
self::PRODUCT_TYPE_ZERO_RATED => 'Z', // ZERO_RATED_GOODS =
|
||||
// self::PRODUCT_TYPE_ZERO_RATED => 'G', // FREE_EXPORT_ITEM =
|
||||
// self::PRODUCT_TYPE_ZERO_RATED => 'O', // OUTSIDE_TAX_SCOPE =
|
||||
// self::PRODUCT_TYPE_EXEMPT => 'K', // EEA_GOODS_AND_SERVICES =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'L', // CANARY_ISLANDS_INDIRECT_TAX =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'M', // CEUTA_AND_MELILLA =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'B', // TRANSFERRED_VAT_ITALY =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'A', // MIXED_TAX_RATE =
|
||||
self::PRODUCT_TYPE_REDUCED_TAX => 'AA', // LOWER_RATE =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'AB', // EXEMPT_FOR_RESALE =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'AC', // VAT_NOT_NOW_DUE =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'AD', // VAT_DUE_PREVIOUS_INVOICE =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'B', // TRANSFERRED_VAT =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'C', // DUTY_PAID_BY_SUPPLIER =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'D', // VAT_MARGIN_SCHEME_TRAVEL_AGENTS =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'F', // VAT_MARGIN_SCHEME_SECOND_HAND_GOODS =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'H', // HIGHER_RATE =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'I', // VAT_MARGIN_SCHEME_WORKS_OF_ART =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'J', // VAT_MARGIN_SCHEME_COLLECTORS_ITEMS =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'K', // VAT_EXEMPT_EEA_INTRA_COMMUNITY =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'L', // CANARY_ISLANDS_TAX =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'M', // TAX_CEUTA_MELILLA =
|
||||
// self::PRODUCT_TYPE_PHYSICAL => 'O', // SERVICES_OUTSIDE_SCOPE =
|
||||
];
|
||||
|
||||
public array $ubl_tax_translations = [
|
||||
'texts.reverse_tax' => 'AE', // VAT_REVERSE_CHARGE
|
||||
'texts.tax_exempt' => 'E', // EXEMPT_FROM_TAX
|
||||
'texts.physical_goods' => 'S', // STANDARD_RATE
|
||||
'texts.zero_rated' => 'Z', // ZERO_RATED_GOODS
|
||||
'ubl.vat_exempt_eea_intra_community' => 'K', // VAT_EXEMPT_EEA_INTRA_COMMUNITY
|
||||
'ubl.free_export_item' => 'G', // FREE_EXPORT_ITEM
|
||||
'ubl.outside_tax_scope' => 'O', // OUTSIDE_TAX_SCOPE
|
||||
'ubl.eea_goods_and_services' => 'K', // EEA_GOODS_AND_SERVICES
|
||||
'ubl.canary_islands_indirect_tax' => 'L', // CANARY_ISLANDS_INDIRECT_TAX
|
||||
'ubl.ceuta_and_melilla' => 'M', // CEUTA_AND_MELILLA
|
||||
'ubl.transferred_vat_italy' => 'B', // TRANSFERRED_VAT_ITALY
|
||||
'ubl.mixed_tax_rate' => 'A', // MIXED_TAX_RATE
|
||||
'ubl.lower_rate' => 'AA', // LOWER_RATE
|
||||
'ubl.exempt_for_resale' => 'AB', // EXEMPT_FOR_RESALE
|
||||
'ubl.vat_not_now_due' => 'AC', // VAT_NOT_NOW_DUE
|
||||
'ubl.vat_due_previous_invoice' => 'AD', // VAT_DUE_PREVIOUS_INVOICE
|
||||
'ubl.transferred_vat' => 'B', // TRANSFERRED_VAT
|
||||
'ubl.duty_paid_by_supplier' => 'C', // DUTY_PAID_BY_SUPPLIER
|
||||
'ubl.vat_margin_scheme_travel_agents' => 'D', // VAT_MARGIN_SCHEME_TRAVEL_AGENTS
|
||||
'ubl.vat_margin_scheme_second_hand_goods' => 'F', // VAT_MARGIN_SCHEME_SECOND_HAND_GOODS
|
||||
'ubl.higher_rate' => 'H', // HIGHER_RATE
|
||||
'ubl.vat_margin_scheme_works_of_art' => 'I', // VAT_MARGIN_SCHEME_WORKS_OF_ART
|
||||
'ubl.vat_margin_scheme_collectors_items' => 'J', // VAT_MARGIN_SCHEME_COLLECTORS_ITEMS
|
||||
'ubl.canary_islands_tax' => 'L', // CANARY_ISLANDS_TAX
|
||||
'ubl.tax_ceuta_melilla' => 'M', // TAX_CEUTA_MELILLA
|
||||
'ubl.services_outside_scope' => 'O', // SERVICES_OUTSIDE_SCOPE
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
||||
public function getEntityType()
|
||||
|
@ -265,7 +265,7 @@ class PurchaseOrder extends BaseModel
|
||||
|
||||
public function expense(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Expense::class);
|
||||
return $this->belongsTo(Expense::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
|
@ -103,7 +103,7 @@ class AuthorizeCustomer
|
||||
} else {
|
||||
// nlog("creating client");
|
||||
|
||||
$first_payment_profile = $profile['payment_profiles'][0];
|
||||
$first_payment_profile = &$profile['payment_profiles'][0];
|
||||
|
||||
if (! $first_payment_profile) {
|
||||
continue;
|
||||
|
@ -101,9 +101,10 @@ class AuthorizePaymentMethod
|
||||
$gateway_customer_reference = (new AuthorizeCreateCustomer($this->authorize, $this->authorize->client))->create($data);
|
||||
$payment_profile = $this->addPaymentMethodToClient($gateway_customer_reference, $data);
|
||||
|
||||
$this->createClientGatewayToken($payment_profile, $gateway_customer_reference);
|
||||
}
|
||||
|
||||
$this->createClientGatewayToken($payment_profile, $gateway_customer_reference);
|
||||
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
@ -168,6 +169,9 @@ class AuthorizePaymentMethod
|
||||
$billto->setCity(substr($this->authorize->client->city, 0, 40));
|
||||
$billto->setState(substr($this->authorize->client->state, 0, 40));
|
||||
$billto->setZip(substr($this->authorize->client->postal_code, 0, 20));
|
||||
|
||||
if(isset($contact->email) && str_contains($contact->email, '@'))
|
||||
$billto->setEmail($contact->email);
|
||||
|
||||
if ($this->authorize->client->country_id) {
|
||||
$billto->setCountry($this->authorize->client->country->name);
|
||||
@ -179,7 +183,7 @@ class AuthorizePaymentMethod
|
||||
// Create a new Customer Payment Profile object
|
||||
$paymentprofile = new CustomerPaymentProfileType();
|
||||
$paymentprofile->setCustomerType('individual');
|
||||
|
||||
|
||||
if ($billto) {
|
||||
$paymentprofile->setBillTo($billto);
|
||||
}
|
||||
|
@ -195,6 +195,8 @@ class AuthorizePaymentDriver extends BaseDriver
|
||||
{
|
||||
$this->init();
|
||||
|
||||
nlog("starting import auth.net");
|
||||
|
||||
return (new AuthorizeCustomer($this))->importCustomers();
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ class ActivityRepository extends BaseRepository
|
||||
* Save the Activity.
|
||||
*
|
||||
* @param \stdClass $fields The fields
|
||||
* @param \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder $entity
|
||||
* @param \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\Expense $entity
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function save($fields, $entity, $event_vars)
|
||||
@ -72,7 +72,7 @@ class ActivityRepository extends BaseRepository
|
||||
/**
|
||||
* Creates a backup.
|
||||
*
|
||||
* @param \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder $entity
|
||||
* @param \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\Expense $entity
|
||||
* @param \App\Models\Activity $activity The activity
|
||||
*/
|
||||
public function createBackup($entity, $activity)
|
||||
|
@ -126,6 +126,21 @@ class ClientRepository extends BaseRepository
|
||||
ClientFactory::create($user->company()->id, $user->id)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk assign clients to a group.
|
||||
*
|
||||
* @param mixed $clients
|
||||
* @param mixed $group_settings_id
|
||||
* @return void
|
||||
*/
|
||||
public function assignGroup($clients, $group_settings_id): void
|
||||
{
|
||||
Client::query()
|
||||
->company()
|
||||
->whereIn('id', $clients->pluck('id'))
|
||||
->update(['group_settings_id' => $group_settings_id]);
|
||||
}
|
||||
|
||||
public function purge($client)
|
||||
{
|
||||
|
@ -46,10 +46,12 @@ class ExpenseRepository extends BaseRepository
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
if(isset($data['payment_date']) && $data['payment_date'] == $expense->payment_date) {
|
||||
$payment_date = &$data['payment_date'];
|
||||
$vendor_id = &$data['vendor_id'];
|
||||
|
||||
if($payment_date && $payment_date == $expense->payment_date) {
|
||||
//do nothing
|
||||
} elseif(isset($data['payment_date']) && strlen($data['payment_date']) > 1 && $user->company()->notify_vendor_when_paid && (isset($data['vendor_id']) || $expense->vendor_id)) {
|
||||
nlog("ping");
|
||||
} elseif($payment_date && strlen($payment_date) > 1 && $user->company()->notify_vendor_when_paid && ($vendor_id || $expense->vendor_id)) {
|
||||
$this->notify_vendor = true;
|
||||
}
|
||||
|
||||
@ -73,6 +75,13 @@ class ExpenseRepository extends BaseRepository
|
||||
VendorExpenseNotify::dispatch($expense, $expense->company->db);
|
||||
}
|
||||
|
||||
if($payment_date && strlen($payment_date) > 1 && $expense->purchase_order) {
|
||||
$purchase_order = $expense->purchase_order;
|
||||
$purchase_order->balance = round($purchase_order->amount - $expense->amount, 2);
|
||||
$purchase_order->paid_to_date = $expense->amount;
|
||||
$purchase_order->save();
|
||||
}
|
||||
|
||||
return $expense;
|
||||
}
|
||||
|
||||
|
119
app/Services/EDocument/Samples/ro.xml
Normal file
119
app/Services/EDocument/Samples/ro.xml
Normal file
@ -0,0 +1,119 @@
|
||||
<?xml version="1.0"?>
|
||||
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
|
||||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
||||
xmlns:ccts="urn:un:unece:uncefact:documentation:2"
|
||||
xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDataTypes-2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:udt="urn:oasis:names:specification:ubl:schema:xsd:UnqualifiedDataTypes-2">
|
||||
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
|
||||
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1</cbc:CustomizationID>
|
||||
<cbc:ID>ABC 0020</cbc:ID>
|
||||
<cbc:IssueDate>2024-01-01</cbc:IssueDate>
|
||||
<cbc:DueDate>2024-01-15</cbc:DueDate>
|
||||
<cbc:InvoiceTypeCode>384</cbc:InvoiceTypeCode>
|
||||
<cbc:DocumentCurrencyCode>RON</cbc:DocumentCurrencyCode>
|
||||
<cbc:TaxCurrencyCode>RON</cbc:TaxCurrencyCode>
|
||||
<cac:AccountingSupplierParty>
|
||||
<cac:Party>
|
||||
<cac:PartyIdentification>
|
||||
<cbc:ID>234234234</cbc:ID>
|
||||
</cac:PartyIdentification>
|
||||
<cac:PostalAddress>
|
||||
<cbc:StreetName>This can be the full address , not just the street and street nr.</cbc:StreetName>
|
||||
<cbc:CityName>SECTOR2</cbc:CityName>
|
||||
<cbc:CountrySubentity>RO-B</cbc:CountrySubentity>
|
||||
<cac:Country>
|
||||
<cbc:IdentificationCode>RO</cbc:IdentificationCode>
|
||||
</cac:Country>
|
||||
</cac:PostalAddress>
|
||||
<cac:PartyTaxScheme>
|
||||
<cbc:CompanyID>RO234234234</cbc:CompanyID>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:PartyTaxScheme>
|
||||
<cac:PartyLegalEntity>
|
||||
<cbc:RegistrationName>Some Copany Name</cbc:RegistrationName>
|
||||
<cbc:CompanyID>J40/2222/2009</cbc:CompanyID>
|
||||
</cac:PartyLegalEntity>
|
||||
<cac:Contact>
|
||||
<cbc:Name>Someone</cbc:Name>
|
||||
<cbc:Telephone>88282819832</cbc:Telephone>
|
||||
<cbc:ElectronicMail>some@email.com</cbc:ElectronicMail>
|
||||
</cac:Contact>
|
||||
</cac:Party>
|
||||
</cac:AccountingSupplierParty>
|
||||
<cac:AccountingCustomerParty>
|
||||
<cac:Party>
|
||||
<cac:PartyIdentification>
|
||||
<cbc:ID>646546549</cbc:ID>
|
||||
</cac:PartyIdentification>
|
||||
<cac:PostalAddress>
|
||||
<cbc:StreetName>This can be the full address , not just the street and street nr.</cbc:StreetName>
|
||||
<cbc:CityName>SECTOR3</cbc:CityName>
|
||||
<cbc:CountrySubentity>RO-B</cbc:CountrySubentity>
|
||||
<cac:Country>
|
||||
<cbc:IdentificationCode>RO</cbc:IdentificationCode>
|
||||
</cac:Country>
|
||||
</cac:PostalAddress>
|
||||
<cac:PartyLegalEntity>
|
||||
<cbc:RegistrationName>Some Comapny</cbc:RegistrationName>
|
||||
<cbc:CompanyID>646546549</cbc:CompanyID>
|
||||
</cac:PartyLegalEntity>
|
||||
<cac:Contact>
|
||||
<cbc:Name>Someone</cbc:Name>
|
||||
<cbc:Telephone></cbc:Telephone>
|
||||
<cbc:ElectronicMail>some@email.com</cbc:ElectronicMail>
|
||||
</cac:Contact>
|
||||
</cac:Party>
|
||||
</cac:AccountingCustomerParty>
|
||||
<cac:PaymentMeans>
|
||||
<cbc:PaymentMeansCode>42</cbc:PaymentMeansCode>
|
||||
<cac:PayeeFinancialAccount>
|
||||
<cbc:ID>some account nr</cbc:ID>
|
||||
<cbc:Name>Bank name</cbc:Name>
|
||||
</cac:PayeeFinancialAccount>
|
||||
</cac:PaymentMeans>
|
||||
<cac:TaxTotal>
|
||||
<cbc:TaxAmount currencyID="RON">63.65</cbc:TaxAmount>
|
||||
<cac:TaxSubtotal>
|
||||
<cbc:TaxableAmount currencyID="RON">335.00</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount currencyID="RON">63.65</cbc:TaxAmount>
|
||||
<cac:TaxCategory>
|
||||
<cbc:ID>S</cbc:ID> // this is a speciffic identifier for the VAT type <cbc:Percent>
|
||||
19</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:TaxCategory>
|
||||
</cac:TaxSubtotal>
|
||||
</cac:TaxTotal>
|
||||
<cac:LegalMonetaryTotal>
|
||||
<cbc:LineExtensionAmount currencyID="RON">335.00</cbc:LineExtensionAmount>
|
||||
<cbc:TaxExclusiveAmount currencyID="RON">335.00</cbc:TaxExclusiveAmount>
|
||||
<cbc:TaxInclusiveAmount currencyID="RON">398.65</cbc:TaxInclusiveAmount>
|
||||
<cbc:AllowanceTotalAmount currencyID="RON">0.00</cbc:AllowanceTotalAmount>
|
||||
<cbc:PayableAmount currencyID="RON">398.65</cbc:PayableAmount>
|
||||
</cac:LegalMonetaryTotal>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>1</cbc:ID>
|
||||
<cbc:InvoicedQuantity unitCode="H87">1</cbc:InvoicedQuantity> // unitcode
|
||||
is a speciffic identifier for the type of product <cbc:LineExtensionAmount currencyID="RON">
|
||||
335.00</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description>Some Description</cbc:Description>
|
||||
<cbc:Name>Some product</cbc:Name>
|
||||
<cac:ClassifiedTaxCategory>
|
||||
<cbc:ID>S</cbc:ID> // this is a speciffic identifier for the VAT type <cbc:Percent>
|
||||
19</cbc:Percent>
|
||||
<cac:TaxScheme>
|
||||
<cbc:ID>VAT</cbc:ID>
|
||||
</cac:TaxScheme>
|
||||
</cac:ClassifiedTaxCategory>
|
||||
</cac:Item>
|
||||
<cac:Price>
|
||||
<cbc:PriceAmount currencyID="RON">335</cbc:PriceAmount>
|
||||
</cac:Price>
|
||||
</cac:InvoiceLine>
|
||||
</Invoice>
|
@ -31,6 +31,7 @@ use CleverIt\UBL\Invoice\TaxCategory;
|
||||
use CleverIt\UBL\Invoice\TaxScheme;
|
||||
use CleverIt\UBL\Invoice\TaxSubTotal;
|
||||
use CleverIt\UBL\Invoice\TaxTotal;
|
||||
use App\Models\Product;
|
||||
|
||||
class RoEInvoice extends AbstractService
|
||||
{
|
||||
@ -65,10 +66,10 @@ class RoEInvoice extends AbstractService
|
||||
$ubl_invoice = new UBLInvoice();
|
||||
|
||||
// invoice
|
||||
$ubl_invoice->setId($invoice->custom_value1 . ' ' . $invoice->number);
|
||||
$ubl_invoice->setId($invoice->number);
|
||||
$ubl_invoice->setIssueDate(date_create($invoice->date));
|
||||
$ubl_invoice->setDueDate(date_create($invoice->due_date));
|
||||
$ubl_invoice->setInvoiceTypeCode(explode('-', $invoice->custom_value3)[0]);
|
||||
$ubl_invoice->setInvoiceTypeCode("380");
|
||||
$ubl_invoice->setDocumentCurrencyCode($invoice->client->getCurrencyCode());
|
||||
$ubl_invoice->setTaxCurrencyCode($invoice->client->getCurrencyCode());
|
||||
|
||||
@ -130,7 +131,7 @@ class RoEInvoice extends AbstractService
|
||||
->setTaxAmount($invoicing_data->getItemTotalTaxes())
|
||||
->setTaxableAmount($taxable)
|
||||
->setTaxCategory((new TaxCategory())
|
||||
->setId(explode('-', $company->settings->custom_value3)[0])
|
||||
->setId("S")
|
||||
->setPercent($taxRatePercent)
|
||||
->setTaxScheme(($taxNameScheme === 'TVA') ? 'VAT' : $taxNameScheme)));
|
||||
$ubl_invoice->setTaxTotal($taxtotal);
|
||||
@ -212,29 +213,29 @@ class RoEInvoice extends AbstractService
|
||||
{
|
||||
if (strlen($item->tax_name1) > 1) {
|
||||
$classifiedTaxCategory = (new ClassifiedTaxCategory())
|
||||
->setId(explode('-', $item->custom_value4)[0])
|
||||
->setId($this->resolveTaxCode($item->tax_id ?? 1))
|
||||
->setPercent($item->tax_rate1)
|
||||
->setTaxScheme(($item->tax_name1 === 'TVA') ? 'VAT' : $item->tax_name1);
|
||||
} elseif (strlen($item->tax_name2) > 1) {
|
||||
$classifiedTaxCategory = (new ClassifiedTaxCategory())
|
||||
->setId(explode('-', $item->custom_value4)[0])
|
||||
->setId($this->resolveTaxCode($item->tax_id ?? 1))
|
||||
->setPercent($item->tax_rate2)
|
||||
->setTaxScheme(($item->tax_name2 === 'TVA') ? 'VAT' : $item->tax_name2);
|
||||
} elseif (strlen($item->tax_name3) > 1) {
|
||||
$classifiedTaxCategory = (new ClassifiedTaxCategory())
|
||||
->setId(explode('-', $item->custom_value4)[0])
|
||||
->setId($this->resolveTaxCode($item->tax_id ?? 1))
|
||||
->setPercent($item->tax_rate3)
|
||||
->setTaxScheme(($item->tax_name3 === 'TVA') ? 'VAT' : $item->tax_name3);
|
||||
}
|
||||
$invoiceLine = (new InvoiceLine())
|
||||
->setId($index + 1)
|
||||
->setInvoicedQuantity($item->quantity)
|
||||
->setUnitCode($item->custom_value3)
|
||||
->setUnitCode($item->unit_code ?? 'C62')
|
||||
->setLineExtensionAmount($item->line_total)
|
||||
->setItem((new Item())
|
||||
->setName($item->product_key)
|
||||
->setDescription($item->notes)
|
||||
->setClassifiedTaxCategory($classifiedTaxCategory))
|
||||
->setClassifiedTaxCategory([$classifiedTaxCategory]))
|
||||
->setPrice((new Price())
|
||||
->setPriceAmount($this->costWithDiscount($item)));
|
||||
|
||||
@ -365,6 +366,25 @@ class RoEInvoice extends AbstractService
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveTaxCode($tax_id)
|
||||
{
|
||||
$code = $tax_id;
|
||||
|
||||
match($tax_id){
|
||||
Product::PRODUCT_TYPE_REVERSE_TAX => $code = 'AE', // VAT_REVERSE_CHARGE =
|
||||
Product::PRODUCT_TYPE_EXEMPT => $code = 'E', // EXEMPT_FROM_TAX =
|
||||
Product::PRODUCT_TYPE_PHYSICAL => $code = 'S', // STANDARD_RATE =
|
||||
Product::PRODUCT_TYPE_ZERO_RATED => $code = 'Z', // ZERO_RATED_GOODS =
|
||||
Product::PRODUCT_TYPE_REDUCED_TAX => $code = 'AA', // LOWER_RATE =
|
||||
Product::PRODUCT_TYPE_SERVICE => $code = 'S', // STANDARD_RATE =
|
||||
Product::PRODUCT_TYPE_DIGITAL => $code = 'S', // STANDARD_RATE =
|
||||
Product::PRODUCT_TYPE_SHIPPING => $code = 'S', // STANDARD_RATE =
|
||||
Product::PRODUCT_TYPE_OVERRIDE_TAX => $code = 'S', // STANDARD_RATE =
|
||||
};
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function generateXml(): string
|
||||
{
|
||||
$ubl_invoice = $this->run(); // Call the existing handle method to get the UBLInvoice
|
||||
|
@ -93,7 +93,7 @@ class Email implements ShouldQueue
|
||||
*/
|
||||
public function backoff()
|
||||
{
|
||||
return [rand(10, 20), rand(30, 45), rand(60, 79), rand(160, 400)];
|
||||
return [rand(5, 29), rand(30, 59), rand(61, 100), rand(180, 500)];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,6 +314,8 @@ class Email implements ShouldQueue
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
$this->cleanUpMailers();
|
||||
|
||||
$this->entityEmailFailed($message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -329,6 +331,8 @@ class Email implements ShouldQueue
|
||||
$this->logMailError($message, $this->company->clients()->first());
|
||||
$this->cleanUpMailers();
|
||||
|
||||
$this->entityEmailFailed($message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -343,11 +347,12 @@ class Email implements ShouldQueue
|
||||
|
||||
if ($message_body && property_exists($message_body, 'Message')) {
|
||||
$message = $message_body->Message;
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$this->fail();
|
||||
$this->entityEmailFailed($message);
|
||||
$this->cleanUpMailers();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -301,7 +301,7 @@ class EmailDefaults
|
||||
$documents = [];
|
||||
|
||||
/* Return early if the user cannot attach documents */
|
||||
if (!$this->email->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT) || $this->email->email_object->email_template_subject == 'email_subject_statement') {
|
||||
if (!$this->email->email_object->invitation || !$this->email->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT) || $this->email->email_object->email_template_subject == 'email_subject_statement') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,9 @@ class GenerateDeliveryNote
|
||||
if($design && $design->is_template) {
|
||||
|
||||
$ts = new TemplateService($design);
|
||||
$pdf = $ts->build([
|
||||
|
||||
$pdf = $ts->setCompany($this->invoice->company)
|
||||
->build([
|
||||
'invoices' => collect([$this->invoice]),
|
||||
])->getPdf();
|
||||
|
||||
|
@ -1173,8 +1173,8 @@ $lang = array(
|
||||
'invoice_number_padding' => 'Padding',
|
||||
'preview' => 'Preview',
|
||||
'list_vendors' => 'List Vendors',
|
||||
'add_users_not_supported' => 'Upgrade to the Enterprise plan to add additional users to your account.',
|
||||
'enterprise_plan_features' => 'The Enterprise plan adds support for multiple users and file attachments, :link to see the full list of features.',
|
||||
'add_users_not_supported' => 'Upgrade to the Enterprise Plan to add additional users to your account.',
|
||||
'enterprise_plan_features' => 'The Enterprise Plan adds support for multiple users and file attachments, :link to see the full list of features.',
|
||||
'return_to_app' => 'Return To App',
|
||||
|
||||
|
||||
@ -1323,7 +1323,7 @@ $lang = array(
|
||||
'security' => 'Security',
|
||||
'see_whats_new' => 'See what\'s new in v:version',
|
||||
'wait_for_upload' => 'Please wait for the document upload to complete.',
|
||||
'upgrade_for_permissions' => 'Upgrade to our Enterprise plan to enable permissions.',
|
||||
'upgrade_for_permissions' => 'Upgrade to our Enterprise Plan to enable permissions.',
|
||||
'enable_second_tax_rate' => 'Enable specifying a <b>second tax rate</b>',
|
||||
'payment_file' => 'Payment File',
|
||||
'expense_file' => 'Expense File',
|
||||
@ -2697,7 +2697,7 @@ $lang = array(
|
||||
'no_assets' => 'No images, drag to upload',
|
||||
'add_image' => 'Add Image',
|
||||
'select_image' => 'Select Image',
|
||||
'upgrade_to_upload_images' => 'Upgrade to the enterprise plan to upload images',
|
||||
'upgrade_to_upload_images' => 'Upgrade to the Enterprise Plan to upload images',
|
||||
'delete_image' => 'Delete Image',
|
||||
'delete_image_help' => 'Warning: deleting the image will remove it from all proposals.',
|
||||
'amount_variable_help' => 'Note: the invoice $amount field will use the partial/deposit field if set otherwise it will use the invoice balance.',
|
||||
@ -3053,7 +3053,7 @@ $lang = array(
|
||||
'valid_until_days' => 'Valid Until',
|
||||
'valid_until_days_help' => 'Automatically sets the <b>Valid Until</b> value on quotes to this many days in the future. Leave blank to disable.',
|
||||
'usually_pays_in_days' => 'Days',
|
||||
'requires_an_enterprise_plan' => 'Requires an enterprise plan',
|
||||
'requires_an_enterprise_plan' => 'Requires an Enterprise Plan',
|
||||
'take_picture' => 'Take Picture',
|
||||
'upload_file' => 'Upload File',
|
||||
'new_document' => 'New Document',
|
||||
@ -3155,7 +3155,7 @@ $lang = array(
|
||||
'archived_group' => 'Successfully archived group',
|
||||
'deleted_group' => 'Successfully deleted group',
|
||||
'restored_group' => 'Successfully restored group',
|
||||
'upload_logo' => 'Upload Logo',
|
||||
'upload_logo' => 'Upload Your Company Logo',
|
||||
'uploaded_logo' => 'Successfully uploaded logo',
|
||||
'saved_settings' => 'Successfully saved settings',
|
||||
'device_settings' => 'Device Settings',
|
||||
@ -3977,7 +3977,7 @@ $lang = array(
|
||||
'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice',
|
||||
'save_payment_method_details' => 'Save payment method details',
|
||||
'new_card' => 'New card',
|
||||
'new_bank_account' => 'New bank account',
|
||||
'new_bank_account' => 'Add Bank Account',
|
||||
'company_limit_reached' => 'Limit of :limit companies per account.',
|
||||
'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices',
|
||||
'credit_number_taken' => 'Credit number already taken',
|
||||
@ -5196,7 +5196,7 @@ $lang = array(
|
||||
'nordigen_handler_error_heading_account_config_invalid' => 'Missing Credentials',
|
||||
'nordigen_handler_error_contents_account_config_invalid' => 'Invalid or missing credentials for Gocardless Bank Account Data. Contact support for help, if this issue persists.',
|
||||
'nordigen_handler_error_heading_not_available' => 'Not Available',
|
||||
'nordigen_handler_error_contents_not_available' => 'Feature unavailable, enterprise plan only.',
|
||||
'nordigen_handler_error_contents_not_available' => 'Feature unavailable, Enterprise Plan only.',
|
||||
'nordigen_handler_error_heading_institution_invalid' => 'Invalid Institution',
|
||||
'nordigen_handler_error_contents_institution_invalid' => 'The provided institution-id is invalid or no longer valid.',
|
||||
'nordigen_handler_error_heading_ref_invalid' => 'Invalid Reference',
|
||||
@ -5288,6 +5288,7 @@ $lang = array(
|
||||
'show_table_footer' => 'Show table footer',
|
||||
'show_table_footer_help' => 'Displays the totals in the footer of the table',
|
||||
'total_invoices' => 'Total Invoices',
|
||||
'add_to_group' => 'Add to group',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
28
lang/en/ubl.php
Normal file
28
lang/en/ubl.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
$lang = array(
|
||||
'free_export_item' => 'Free export item',
|
||||
'outside_tax_scope' => 'Outside tax scope',
|
||||
'eea_goods_and_services' => 'EEA goods and services',
|
||||
'lower_rate' => 'Lower rate',
|
||||
'mixed_tax_rate' => 'Mixed tax rate',
|
||||
'higher_rate' => 'Higher rate',
|
||||
'canary_islands_indirect_tax' => 'Canary Islands indirect tax',
|
||||
'ceuta_and_melilla' => 'Ceuta and Melilla',
|
||||
'transferred_vat_italy' => 'Transferred VAT Italy',
|
||||
'exempt_for_resale' => 'Exempt for resale',
|
||||
'vat_not_now_due' => 'VAT not now due',
|
||||
'vat_due_previous_invoice' => 'VAT due previous',
|
||||
'transferred_vat' => 'Transferred VAT',
|
||||
'duty_paid_by_supplier' => 'Duty paid by supplier',
|
||||
'vat_margin_scheme_travel_agents' => 'VAT margin scheme travel agents',
|
||||
'vat_margin_scheme_second_hand_goods' => 'VAT margin scheme second hand goods',
|
||||
'vat_margin_scheme_works_of_art' => 'VAT margin scheme works of art',
|
||||
'vat_margin_scheme_collectors_items' => 'VAT margin scheme collectors items',
|
||||
'vat_exempt_eea_intra_community' => 'VAT exempt EEA intra community',
|
||||
'canary_islands_tax' => 'Canary Islands tax',
|
||||
'tax_ceuta_melilla' => 'Tax Ceuta Melilla',
|
||||
'services_outside_scope' => 'Services outside scope',
|
||||
);
|
||||
|
||||
return $lang;
|
@ -101,7 +101,7 @@
|
||||
flex-direction: column;
|
||||
line-height: var(--line-height);
|
||||
white-space: nowrap;
|
||||
border: 1px solid #000;
|
||||
border: 0px solid #000;
|
||||
}
|
||||
|
||||
[data-ref="table"] {
|
||||
|
@ -21,12 +21,15 @@ use App\Models\Currency;
|
||||
use Tests\MockAccountData;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\ClientContact;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Tests\Unit\GroupSettingsTest;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\DataMapper\DefaultSettings;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Factory\GroupSettingFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@ -69,6 +72,45 @@ class ClientTest extends TestCase
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function testBulkGroupAssignment()
|
||||
{
|
||||
Client::factory()->count(5)->create(['user_id' => $this->user->id, 'company_id' => $this->company->id])->each(function ($c) {
|
||||
ClientContact::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $c->id,
|
||||
'company_id' => $this->company->id,
|
||||
'is_primary' => 1,
|
||||
]);
|
||||
});
|
||||
|
||||
$gs = GroupSettingFactory::create($this->company->id, $this->user->id);
|
||||
$gs->name = 'testtest';
|
||||
$gs->save();
|
||||
|
||||
$ids = Client::where('company_id', $this->company->id)->get()->pluck('hashed_id')->toArray();
|
||||
$data = [
|
||||
'action' => 'assign_group',
|
||||
'ids' => $ids,
|
||||
'group_settings_id' => $gs->hashed_id,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/bulk', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
Client::query()->whereIn('id', $this->transformKeys($ids))->cursor()->each(function ($c) use ($gs, $arr) {
|
||||
$this->assertEquals($gs->id, $c->group_settings_id);
|
||||
});
|
||||
|
||||
foreach($arr['data'] as $client_response){
|
||||
|
||||
$this->assertEquals($gs->hashed_id, $client_response['group_settings_id']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testClientExchangeRateCalculation()
|
||||
{
|
||||
$settings = ClientSettings::defaults();
|
||||
|
@ -12,13 +12,19 @@
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Models\User;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\Models\RecurringInvoice;
|
||||
use Tests\MockAccountData;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\Subscription;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Factory\CompanyUserFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@ -51,6 +57,250 @@ class SubscriptionApiTest extends TestCase
|
||||
Model::reguard();
|
||||
}
|
||||
|
||||
public function testSubscriptionCronLocalization()
|
||||
{
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->timezone_id = '50'; //europe/vienna
|
||||
|
||||
$c2 = Company::factory()->create([
|
||||
'account_id' => $this->company->account_id,
|
||||
'settings' => $settings
|
||||
]);
|
||||
|
||||
$cu = CompanyUserFactory::create($this->user->id, $c2->id, $this->account->id);
|
||||
$cu->is_owner = true;
|
||||
$cu->is_admin = true;
|
||||
$cu->is_locked = true;
|
||||
$cu->permissions = '["view_client"]';
|
||||
$cu->save();
|
||||
|
||||
$different_company_token = \Illuminate\Support\Str::random(64);
|
||||
|
||||
$company_token = new CompanyToken();
|
||||
$company_token->user_id = $this->user->id;
|
||||
$company_token->company_id = $c2->id;
|
||||
$company_token->account_id = $c2->account_id;
|
||||
$company_token->name = 'test token';
|
||||
$company_token->token = $different_company_token;
|
||||
$company_token->is_system = true;
|
||||
$company_token->save();
|
||||
|
||||
|
||||
$s = Subscription::factory()->create([
|
||||
'company_id' => $c2->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$client2 = Client::factory()->create([
|
||||
'company_id' => $c2->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$i = Invoice::factory()->create([
|
||||
'company_id' => $c2->id,
|
||||
'user_id' => $this->user->id,
|
||||
'subscription_id' => $s->id,
|
||||
'due_date' => now()->startOfDay(),
|
||||
'client_id' => $client2->id,
|
||||
'status_id' => Invoice::STATUS_SENT
|
||||
]);
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->timezone_id = '110'; //sydney/australia
|
||||
|
||||
$c = Company::factory()->create([
|
||||
'account_id' => $this->company->account_id,
|
||||
'settings' => $settings,
|
||||
]);
|
||||
|
||||
$cu = CompanyUserFactory::create($this->user->id, $c->id, $this->account->id);
|
||||
$cu->is_owner = true;
|
||||
$cu->is_admin = true;
|
||||
$cu->is_locked = true;
|
||||
$cu->permissions = '["view_client"]';
|
||||
$cu->save();
|
||||
|
||||
$different_company_token = \Illuminate\Support\Str::random(64);
|
||||
|
||||
$company_token = new CompanyToken();
|
||||
$company_token->user_id = $this->user->id;
|
||||
$company_token->company_id = $c->id;
|
||||
$company_token->account_id = $c->account_id;
|
||||
$company_token->name = 'test token';
|
||||
$company_token->token = $different_company_token;
|
||||
$company_token->is_system = true;
|
||||
$company_token->save();
|
||||
|
||||
$s1 = Subscription::factory()->create([
|
||||
'company_id' => $c->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
|
||||
$client = Client::factory()->create([
|
||||
'company_id' => $c2->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$i = Invoice::factory()->create([
|
||||
'company_id' => $c->id,
|
||||
'user_id' => $this->user->id,
|
||||
'subscription_id' => $s1->id,
|
||||
'due_date' => now()->startOfDay(),
|
||||
'client_id' => $client->id,
|
||||
'status_id' => Invoice::STATUS_SENT
|
||||
]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
$company = Company::find($c->id); //sydney
|
||||
|
||||
$timezone_now = now()->setTimezone($company->timezone()->name);
|
||||
|
||||
$this->assertEquals('Australia/Sydney', $timezone_now->timezoneName);
|
||||
|
||||
$this->travelTo($timezone_now->copy()->startOfDay()->subHour());
|
||||
|
||||
$i = false;
|
||||
|
||||
//Capture companies within the window of 00:00 and 00:30
|
||||
if($timezone_now->gte($timezone_now->copy()->startOfDay()) && $timezone_now->lt($timezone_now->copy()->startOfDay()->addMinutes(30))) {
|
||||
|
||||
$i = Invoice::query()
|
||||
->where('company_id', $company->id)
|
||||
->whereNull('deleted_at')
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('is_proforma', 0)
|
||||
->whereNotNull('subscription_id')
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->setTimezone($company->timezone()->name)->addDay()->startOfDay())
|
||||
->get();
|
||||
|
||||
}
|
||||
|
||||
$this->assertFalse($i);
|
||||
|
||||
$this->travelTo($timezone_now->copy()->startOfDay());
|
||||
|
||||
if(now()->gte($timezone_now->copy()->startOfDay()) && now()->lt($timezone_now->copy()->startOfDay()->addMinutes(30))) {
|
||||
|
||||
$i = Invoice::query()
|
||||
->where('company_id', $company->id)
|
||||
->whereNull('deleted_at')
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('is_proforma', 0)
|
||||
->whereNotNull('subscription_id')
|
||||
->whereDate('due_date', '<=', now()->setTimezone($company->timezone()->name)->addDay()->startOfDay())
|
||||
->get();
|
||||
|
||||
}
|
||||
|
||||
$this->assertEquals(1, $i->count());
|
||||
|
||||
$i = false;
|
||||
|
||||
$this->travelTo($timezone_now->copy()->startOfDay()->addHours(2));
|
||||
|
||||
if($timezone_now->gte($timezone_now->copy()->startOfDay()) && $timezone_now->lt($timezone_now->copy()->startOfDay()->addMinutes(30))) {
|
||||
|
||||
$i = Invoice::query()
|
||||
->where('company_id', $company->id)
|
||||
->whereNull('deleted_at')
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('is_proforma', 0)
|
||||
->whereNotNull('subscription_id')
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->setTimezone($company->timezone()->name)->addDay()->startOfDay())
|
||||
->get();
|
||||
|
||||
}
|
||||
|
||||
$this->assertFalse($i);
|
||||
|
||||
$count = Invoice::whereNotNull('subscription_id')->count();
|
||||
|
||||
$this->assertEquals(2, $count);
|
||||
|
||||
$this->travelBack();
|
||||
//////////////////////////////////////////// vienna //////////////////////////////////////////////////
|
||||
|
||||
$company = Company::find($c2->id); //vienna
|
||||
|
||||
$timezone_now = now()->setTimezone($company->timezone()->name);
|
||||
|
||||
$this->assertEquals('Europe/Vienna', $timezone_now->timezoneName);
|
||||
|
||||
$this->travelTo($timezone_now->copy()->startOfDay()->subHour());
|
||||
|
||||
$this->travelTo($timezone_now->copy()->startOfDay()->subHour());
|
||||
|
||||
$i = false;
|
||||
|
||||
//Capture companies within the window of 00:00 and 00:30
|
||||
if($timezone_now->gte($timezone_now->copy()->startOfDay()) && $timezone_now->lt($timezone_now->copy()->startOfDay()->addMinutes(30))) {
|
||||
|
||||
$i = Invoice::query()
|
||||
->where('company_id', $company->id)
|
||||
->whereNull('deleted_at')
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('is_proforma', 0)
|
||||
->whereNotNull('subscription_id')
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->setTimezone($company->timezone()->name)->addDay()->startOfDay())
|
||||
->get();
|
||||
|
||||
}
|
||||
|
||||
$this->assertFalse($i);
|
||||
|
||||
$this->travelTo($timezone_now->copy()->startOfDay());
|
||||
|
||||
if(now()->gte($timezone_now->copy()->startOfDay()) && now()->lt($timezone_now->copy()->startOfDay()->addMinutes(30))) {
|
||||
|
||||
$i = Invoice::query()
|
||||
->where('company_id', $company->id)
|
||||
->whereNull('deleted_at')
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('is_proforma', 0)
|
||||
->whereNotNull('subscription_id')
|
||||
->whereDate('due_date', '<=', now()->setTimezone($company->timezone()->name)->addDay()->startOfDay())
|
||||
->get();
|
||||
|
||||
}
|
||||
|
||||
$this->assertEquals(1, $i->count());
|
||||
|
||||
$i = false;
|
||||
|
||||
$this->travelTo($timezone_now->copy()->startOfDay()->addHours(2));
|
||||
|
||||
if($timezone_now->gte($timezone_now->copy()->startOfDay()) && $timezone_now->lt($timezone_now->copy()->startOfDay()->addMinutes(30))) {
|
||||
|
||||
$i = Invoice::query()
|
||||
->where('company_id', $company->id)
|
||||
->whereNull('deleted_at')
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('is_proforma', 0)
|
||||
->whereNotNull('subscription_id')
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->setTimezone($company->timezone()->name)->addDay()->startOfDay())
|
||||
->get();
|
||||
|
||||
}
|
||||
|
||||
$this->assertFalse($i);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testAssignInvoice()
|
||||
{
|
||||
$i = Invoice::factory()
|
||||
|
Loading…
x
Reference in New Issue
Block a user