forked from invoiceninja/invoiceninja
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
557 additions
and
5 deletions.
There are no files selected for viewing
244 changes: 244 additions & 0 deletions
244
app/Services/EDocument/Gateway/Storecove/PeppolToStorecoveNormalizer.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
<?php | ||
|
||
namespace App\Services\EDocument\Gateway\Storecove; | ||
|
||
use App\Services\EDocument\Gateway\Storecove\Models\Tax; | ||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; | ||
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; | ||
use App\Services\EDocument\Gateway\Storecove\Models\InvoiceLines; | ||
use InvoiceNinja\EInvoice\Models\Peppol\Invoice as PeppolInvoice; | ||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; | ||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; | ||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; | ||
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; | ||
use App\Services\EDocument\Gateway\Storecove\Models\Invoice as StorecoveInvoice; | ||
use Symfony\Component\Serializer\SerializerInterface; | ||
|
||
|
||
class PeppolToStorecoveNormalizer implements DenormalizerInterface | ||
{ | ||
private SerializerInterface $serializer; | ||
private ObjectNormalizer $objectNormalizer; | ||
|
||
|
||
public function __construct(SerializerInterface $serializer) | ||
{ | ||
|
||
|
||
$this->serializer = $serializer; | ||
|
||
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); | ||
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); | ||
$this->objectNormalizer = new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter); | ||
|
||
} | ||
|
||
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed | ||
{ | ||
$peppolInvoice = $data; | ||
$storecoveInvoice = new StorecoveInvoice(); | ||
|
||
|
||
$storecoveInvoice->setDocumentCurrency($peppolInvoice->DocumentCurrencyCode ?? ''); | ||
$storecoveInvoice->setInvoiceNumber($peppolInvoice->ID ?? ''); | ||
$storecoveInvoice->setIssueDate($peppolInvoice->IssueDate); | ||
$storecoveInvoice->setDueDate($peppolInvoice->DueDate); | ||
$storecoveInvoice->setNote($peppolInvoice->Note ?? ''); | ||
$storecoveInvoice->setAmountIncludingVat((float)($peppolInvoice->LegalMonetaryTotal->TaxInclusiveAmount->amount ?? 0)); | ||
|
||
if (isset($peppolInvoice->InvoicePeriod[0])) { | ||
$storecoveInvoice->setInvoicePeriod([ | ||
'startDate' => $peppolInvoice->InvoicePeriod[0]->StartDate, | ||
'endDate' => $peppolInvoice->InvoicePeriod[0]->EndDate, | ||
]); | ||
} | ||
|
||
$storecoveInvoice->setReferences([ | ||
'buyerReference' => $peppolInvoice->BuyerReference ?? '', | ||
'orderReference' => $peppolInvoice->OrderReference->ID->value ?? '', | ||
]); | ||
|
||
if (isset($peppolInvoice->AccountingSupplierParty->Party)) { | ||
$supplier = $peppolInvoice->AccountingSupplierParty->Party; | ||
$storecoveInvoice->setAccountingSupplierParty([ | ||
'name' => $supplier->PartyName[0]->Name ?? '', | ||
'vatNumber' => $supplier->PartyIdentification[0]->ID->value ?? '', | ||
'streetName' => $supplier->PostalAddress->StreetName ?? '', | ||
'cityName' => $supplier->PostalAddress->CityName ?? '', | ||
'postalZone' => $supplier->PostalAddress->PostalZone ?? '', | ||
'countryCode' => $supplier->PostalAddress->Country->IdentificationCode->value ?? '', | ||
]); | ||
} | ||
|
||
if (isset($peppolInvoice->AccountingCustomerParty->Party)) { | ||
$customer = $peppolInvoice->AccountingCustomerParty->Party; | ||
$storecoveInvoice->setAccountingCustomerParty([ | ||
'name' => $customer->PartyName[0]->Name ?? '', | ||
'vatNumber' => $customer->PartyIdentification[0]->ID->value ?? '', | ||
'streetName' => $customer->PostalAddress->StreetName ?? '', | ||
'cityName' => $customer->PostalAddress->CityName ?? '', | ||
'postalZone' => $customer->PostalAddress->PostalZone ?? '', | ||
'countryCode' => $customer->PostalAddress->Country->IdentificationCode->value ?? '', | ||
]); | ||
} | ||
|
||
if (isset($peppolInvoice->PaymentMeans[0])) { | ||
$storecoveInvoice->setPaymentMeans([ | ||
'paymentID' => $peppolInvoice->PaymentMeans[0]->PayeeFinancialAccount->ID->value ?? '', | ||
]); | ||
} | ||
|
||
// Map tax total at invoice level | ||
$taxTotal = []; | ||
if (isset($peppolInvoice->InvoiceLine[0]->TaxTotal[0])) { | ||
$taxTotal[] = [ | ||
'taxAmount' => (float)($peppolInvoice->InvoiceLine[0]->TaxTotal[0]->TaxAmount->amount ?? 0), | ||
'taxCurrency' => $peppolInvoice->DocumentCurrencyCode ?? '', | ||
]; | ||
} | ||
$storecoveInvoice->setTaxTotal($taxTotal); | ||
|
||
if (isset($peppolInvoice->InvoiceLine)) { | ||
$invoiceLines = []; | ||
foreach ($peppolInvoice->InvoiceLine as $line) { | ||
$invoiceLine = new InvoiceLines(); | ||
$invoiceLine->setLineId($line->ID->value ?? ''); | ||
$invoiceLine->setAmountExcludingVat((float)($line->LineExtensionAmount->amount ?? 0)); | ||
$invoiceLine->setQuantity((float)($line->InvoicedQuantity ?? 0)); | ||
$invoiceLine->setQuantityUnitCode(''); // Not present in the provided JSON | ||
$invoiceLine->setItemPrice((float)($line->Price->PriceAmount->amount ?? 0)); | ||
$invoiceLine->setName($line->Item->Name ?? ''); | ||
$invoiceLine->setDescription($line->Item->Description ?? ''); | ||
|
||
$tax = new Tax(); | ||
if (isset($line->TaxTotal[0])) { | ||
$taxTotal = $line->TaxTotal[0]; | ||
$tax->setTaxAmount((float)($taxTotal->TaxAmount->amount ?? 0)); | ||
|
||
if (isset($line->Item->ClassifiedTaxCategory[0])) { | ||
$taxCategory = $line->Item->ClassifiedTaxCategory[0]; | ||
$tax->setTaxPercentage((float)($taxCategory->Percent ?? 0)); | ||
$tax->setTaxCategory($taxCategory->ID->value ?? ''); | ||
} | ||
|
||
$tax->setTaxableAmount((float)($line->LineExtensionAmount->amount ?? 0)); | ||
} | ||
$invoiceLine->setTax($tax); | ||
|
||
$invoiceLines[] = $invoiceLine; | ||
} | ||
$storecoveInvoice->setInvoiceLines($invoiceLines); | ||
} | ||
|
||
return $storecoveInvoice; | ||
|
||
|
||
} | ||
|
||
private function validateStorecoveInvoice(StorecoveInvoice $invoice): void | ||
{ | ||
$requiredFields = ['documentCurrency', 'invoiceNumber', 'issueDate', 'dueDate']; | ||
foreach ($requiredFields as $field) { | ||
if (empty($invoice->$field)) { | ||
throw new \InvalidArgumentException("Required field '$field' is missing or empty"); | ||
} | ||
} | ||
|
||
if (empty($invoice->invoiceLines)) { | ||
throw new \InvalidArgumentException("Invoice must have at least one line item"); | ||
} | ||
|
||
// Add more validations as needed | ||
} | ||
|
||
public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool | ||
{ | ||
return $type === StorecoveInvoice::class && $data instanceof PeppolInvoice; | ||
} | ||
|
||
public function getSupportedTypes(?string $format): array | ||
{ | ||
return [ | ||
StorecoveInvoice::class => true, | ||
]; | ||
} | ||
|
||
private function mapNestedProperties($object, array $nestedPaths): array | ||
{ | ||
$result = []; | ||
foreach ($nestedPaths as $key => $path) { | ||
if (is_array($path)) { | ||
// Try multiple paths | ||
foreach ($path as $possiblePath) { | ||
$value = $this->getValueFromPath($object, $possiblePath); | ||
if ($value !== null) { | ||
$result[$key] = $value; | ||
nlog("Mapped nested property: $key", ['path' => $possiblePath, 'value' => $value]); | ||
break; | ||
} | ||
} | ||
if (!isset($result[$key])) { | ||
nlog("Failed to map nested property: $key", ['paths' => $path]); | ||
} | ||
} else { | ||
$value = $this->getValueFromPath($object, $path); | ||
if ($value !== null) { | ||
$result[$key] = $value; | ||
nlog("Mapped nested property: $key", ['path' => $path, 'value' => $value]); | ||
} else { | ||
nlog("Failed to map nested property: $key", ['path' => $path]); | ||
} | ||
} | ||
} | ||
return $result; | ||
} | ||
|
||
private function getValueFromPath($object, string $path) | ||
{ | ||
$parts = explode('.', $path); | ||
$value = $object; | ||
foreach ($parts as $part) { | ||
if (preg_match('/(.+)\[(\d+)\]/', $part, $matches)) { | ||
$property = $matches[1]; | ||
$index = $matches[2]; | ||
$value = $value->$property[$index] ?? null; | ||
} else { | ||
$value = $value->$part ?? null; | ||
} | ||
if ($value === null) { | ||
nlog("Null value encountered in path: $path at part: $part"); | ||
return null; | ||
} | ||
} | ||
return $value instanceof \DateTime ? $value->format('Y-m-d') : $value; | ||
} | ||
|
||
private function castValue(string $property, $value) | ||
{ | ||
try { | ||
$reflectionProperty = new \ReflectionProperty(StorecoveInvoice::class, $property); | ||
$type = $reflectionProperty->getType(); | ||
|
||
if ($type instanceof \ReflectionNamedType) { | ||
switch ($type->getName()) { | ||
case 'float': | ||
return (float) $value; | ||
case 'int': | ||
return (int) $value; | ||
case 'string': | ||
return (string) $value; | ||
case 'bool': | ||
return (bool) $value; | ||
case 'array': | ||
return (array) $value; | ||
default: | ||
return $value; | ||
} | ||
} | ||
} catch (\ReflectionException $e) { | ||
nlog("Error casting value for property: $property", ['error' => $e->getMessage()]); | ||
} | ||
|
||
return $value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2345,7 +2345,7 @@ | |
'currency_gold_troy_ounce' => 'أونصة تروي ذهبية', | ||
'currency_nicaraguan_córdoba' => 'قرطبة نيكاراغوا', | ||
'currency_malagasy_ariary' => 'أرياري مدغشقر', | ||
"currency_tongan_paanga" => "بانغا تونغا", | ||
"currency_tongan_pa_anga" => "بانغا تونغا", | ||
|
||
'review_app_help' => 'نأمل أن تستمتع باستخدام التطبيق.<br/> إذا كنت تفكر في :link فإننا نقدر ذلك كثيرًا!', | ||
'writing_a_review' => 'كتابة مراجعة', | ||
|
@@ -5319,6 +5319,73 @@ | |
'no_unread_notifications' => 'لقد تم اللحاق بكل شيء! لا توجد إشعارات جديدة.', | ||
'how_to_import_data' => 'كيفية استيراد البيانات', | ||
'download_example_file' => 'تنزيل ملف المثال', | ||
'expense_mailbox' => 'عنوان البريد الإلكتروني الوارد', | ||
'expense_mailbox_help' => 'عنوان البريد الإلكتروني الوارد الذي يقبل مستندات النفقات. على سبيل المثال، [email protected]', | ||
'expense_mailbox_active' => 'صندوق بريد النفقات', | ||
'expense_mailbox_active_help' => 'يتيح معالجة المستندات مثل الإيصالات لإعداد تقارير النفقات', | ||
'inbound_mailbox_allow_company_users' => 'السماح لمرسلي الشركة', | ||
'inbound_mailbox_allow_company_users_help' => 'يسمح للمستخدمين داخل الشركة بإرسال مستندات النفقات.', | ||
'inbound_mailbox_allow_vendors' => 'السماح لمرسلي البائعين', | ||
'inbound_mailbox_allow_vendors_help' => 'يسمح لبائعي الشركة بإرسال مستندات النفقات', | ||
'inbound_mailbox_allow_clients' => 'السماح لمرسلي العميل', | ||
'inbound_mailbox_allow_clients_help' => 'يسمح للعملاء بإرسال مستندات النفقات', | ||
'inbound_mailbox_whitelist' => 'قائمة السماح للمرسلين الواردين', | ||
'inbound_mailbox_whitelist_help' => 'قائمة منفصلة بفواصل للرسائل الإلكترونية التي ينبغي السماح لها بإرسال رسائل إلكترونية للمعالجة', | ||
'inbound_mailbox_blacklist' => 'قائمة المرسلين المحظورين', | ||
'inbound_mailbox_blacklist_help' => 'قائمة منفصلة بفواصل للرسائل الإلكترونية التي لا يُسمح بإرسال رسائل إلكترونية إليها للمعالجة', | ||
'inbound_mailbox_allow_unknown' => 'السماح لجميع المرسلين', | ||
'inbound_mailbox_allow_unknown_help' => 'السماح لأي شخص بإرسال بريد إلكتروني للنفقات للمعالجة', | ||
'quick_actions' => 'الإجراءات السريعة', | ||
'end_all_sessions_help' => 'يقوم بتسجيل خروج جميع المستخدمين ويطلب من جميع المستخدمين النشطين إعادة المصادقة.', | ||
'updated_records' => 'السجلات المحدثة', | ||
'vat_not_registered' => 'البائع غير مسجل في ضريبة القيمة المضافة', | ||
'small_company_info' => 'عدم الإفصاح عن ضريبة المبيعات وفقًا للمادة 19 من قانون الضرائب الأمريكي', | ||
'peppol_onboarding' => 'يبدو أن هذه هي المرة الأولى التي تستخدم فيها PEPPOL', | ||
'get_started' => 'البدء', | ||
'configure_peppol' => 'Configure PEPPOL', | ||
'step' => 'خطوة', | ||
'peppol_whitelabel_warning' => 'White-label license required in order to use einvoicing over the PEPPOL network.', | ||
'peppol_plan_warning' => 'Enterprise plan required in order to use einvoicing over the PEPPOL network.', | ||
'peppol_credits_info' => 'Ecredits are required to send and receive einvoices. These are charged on a per document basis.', | ||
'buy_credits' => 'Buy E Credits', | ||
'peppol_successfully_configured' => 'PEPPOL successsfully configured.', | ||
'peppol_not_paid_message' => 'Enterprise plan required for PEPPOL. Please upgrade your plan.', | ||
'peppol_country_not_supported' => 'PEPPOL network not yet available for this country.', | ||
'peppol_disconnect' => 'Disconnect from the PEPPOL network', | ||
'peppol_disconnect_short' => 'Disconnect from PEPPOL.', | ||
'peppol_disconnect_long' => 'Your VAT number will be withdrawn from the PEPPOL network after disconnecting. You will be unable to send or receive edocuments.', | ||
'log_duration_words' => 'مدة تسجيل الوقت بالكلمات', | ||
'log_duration' => 'مدة سجل الوقت', | ||
'merged_vendors' => 'تم دمج البائعين بنجاح', | ||
'hidden_taxes_warning' => 'بعض الضرائب مخفية بسبب إعدادات الضرائب الحالية. :link', | ||
'tax3' => 'الضريبة الثالثة', | ||
'negative_payment_warning' => 'هل أنت متأكد من أنك تريد إنشاء دفعة سلبية؟ لا يمكن استخدام هذه الدفعة كرصيد أو دفعة.', | ||
'currency_Bermudian_Dollar' => 'الدولار البرمودي', | ||
'currency_Central_African_CFA_Franc' => 'الفرنك الوسط أفريقي', | ||
'currency_Congolese_Franc' => 'الفرنك الكونغولي', | ||
'currency_Djiboutian_Franc' => 'فرنك جيبوتي', | ||
'currency_Eritrean_Nakfa' => 'الناكفا الإريترية', | ||
'currency_Falkland_Islands_Pound' => 'Falklan Islands Pound', | ||
'currency_Guinean_Franc' => 'فرنك غيني', | ||
'currency_Iraqi_Dinar' => 'الدينار العراقي', | ||
'currency_Lesotho_Loti' => 'ليسوتو لوتي', | ||
'currency_Mongolian_Tugrik' => 'التوغريك المنغولي', | ||
'currency_Seychellois_Rupee' => 'الروبية السيشيلية', | ||
'currency_Solomon_Islands_Dollar' => 'دولار جزر سليمان', | ||
'currency_Somali_Shilling' => 'شلن صومالي', | ||
'currency_South_Sudanese_Pound' => 'الجنيه الجنوب سوداني', | ||
'currency_Sudanese_Pound' => 'الجنيه السوداني', | ||
'currency_Tajikistani_Somoni' => 'السوموني الطاجيكستاني', | ||
'currency_Turkmenistani_Manat' => 'المانات التركمانستانية', | ||
'currency_Uzbekistani_Som' => 'السوم الأوزباكستاني', | ||
'payment_status_changed' => 'يرجى ملاحظة أن حالة الدفع الخاصة بك قد تم تحديثها. نوصي بتحديث الصفحة لعرض الإصدار الأحدث.', | ||
'credit_status_changed' => 'يرجى ملاحظة أن حالة الائتمان الخاصة بك قد تم تحديثها. نوصي بتحديث الصفحة لعرض الإصدار الأحدث.', | ||
'credit_updated' => 'تم تحديث الائتمان', | ||
'payment_updated' => 'تم تحديث الدفع', | ||
'search_placeholder' => 'ابحث عن الفواتير والعملاء والمزيد', | ||
'invalid_vat_number' => "رقم ضريبة القيمة المضافة غير صالح للبلد المحدد. يجب أن يكون التنسيق عبارة عن رمز البلد متبوعًا برقم فقط، على سبيل المثال، DE123456789", | ||
'acts_as_sender' => 'Acts as Sender', | ||
'acts_as_receiver' => 'Acts as Receiver', | ||
); | ||
|
||
return $lang; |
Oops, something went wrong.