| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.ofbiz.accounting.invoice |
| |
| import org.apache.ofbiz.accounting.util.UtilAccounting |
| import org.apache.ofbiz.base.util.Debug |
| import org.apache.ofbiz.base.util.UtilDateTime |
| import org.apache.ofbiz.base.util.UtilFormatOut |
| import org.apache.ofbiz.base.util.UtilValidate |
| import org.apache.ofbiz.entity.GenericValue |
| import org.apache.ofbiz.entity.condition.EntityCondition |
| import org.apache.ofbiz.entity.condition.EntityConditionBuilder |
| import org.apache.ofbiz.entity.util.EntityTypeUtil |
| import org.apache.ofbiz.entity.util.EntityUtil |
| import org.apache.ofbiz.entity.util.EntityUtilProperties |
| import org.apache.ofbiz.service.ServiceUtil |
| |
| import java.sql.Timestamp |
| |
| Map getNextInvoiceId() { |
| Map result = success() |
| |
| // try to find PartyAcctgPreference for parameters.partyId, see if we need any special invoice number sequencing |
| GenericValue partyAcctgPreference = from('PartyAcctgPreference').where(parameters).queryOne() |
| if (Debug.infoOn()) { |
| logInfo("In getNextInvoiceId partyId is [${parameters.partyId}], partyAcctgPreference: ${partyAcctgPreference}") |
| } |
| |
| String customMethodName = null |
| String invoiceIdPrefix = '' |
| if (partyAcctgPreference) { |
| invoiceIdPrefix = partyAcctgPreference.invoiceIdPrefix ?: '' |
| //see OFBIZ-3765 beware of OFBIZ-3557 |
| GenericValue customMethod = partyAcctgPreference.getRelatedOne('InvoiceCustomMethod', true) |
| if (customMethod) { |
| customMethodName = customMethod.customMethodName |
| } else { |
| //retrieve service from deprecated enumeration see OFBIZ-3765 beware of OFBIZ-3557 |
| if (partyAcctgPreference.oldInvoiceSequenceEnumId == 'INVSQ_ENF_SEQ') { |
| customMethodName = 'invoiceSequenceEnforced' |
| } |
| if (partyAcctgPreference.oldInvoiceSequenceEnumId == 'INVSQ_RESTARTYR') { |
| customMethodName = 'invoiceSequenceRestart' |
| } |
| } |
| } else { |
| logWarning("Acctg preference not defined for partyId [${parameters.partyId}]") |
| } |
| |
| String invoiceIdTemp = '' |
| if (customMethodName) { |
| parameters.partyAcctgPreference = partyAcctgPreference |
| Map serviceResult = run service: customMethodName, with: parameters |
| if (ServiceUtil.isError(serviceResult)) { |
| return serviceResult |
| } |
| invoiceIdTemp = serviceResult.invoiceId |
| } else { |
| logInfo('In createInvoice sequence enum Standard') |
| //default to the default sequencing: INVSQ_STANDARD |
| invoiceIdTemp = parameters.invoiceId |
| if (invoiceIdTemp) { |
| //check the provided ID |
| errorMsg = UtilValidate.checkValidDatabaseId(invoiceIdTemp) |
| if (errorMsg != null) { |
| return error("In getNextInvoiceId ${errorMsg}") |
| } |
| } else { |
| invoiceIdTemp = delegator.getNextSeqId('Invoice', 1) |
| } |
| } |
| |
| // use invoiceIdTemp along with the invoiceIdPrefix to create the real ID |
| result.invoiceId = invoiceIdPrefix + invoiceIdTemp |
| return result |
| } |
| |
| Map invoiceSequenceEnforced() { |
| Map result = success() |
| |
| logInfo('In createInvoice sequence enum Enforced') |
| GenericValue partyAcctgPreference = parameters.partyAcctgPreference |
| //this is sequential sequencing, we can't skip a number, also it must be a unique sequence per partyIdFrom |
| |
| Long lastInvoiceNumber = 1 |
| if (partyAcctgPreference.lastInvoiceNumber) { |
| lastInvoiceNumber = partyAcctgPreference.lastInvoiceNumber + 1 |
| } |
| |
| partyAcctgPreference.lastInvoiceNumber = lastInvoiceNumber |
| delegator.store(partyAcctgPreference) |
| result.invoiceId = lastInvoiceNumber |
| return result |
| } |
| |
| Map invoiceSequenceRestart() { |
| logInfo('In createInvoice sequence enum Enforced') |
| GenericValue partyAcctgPreference = parameters.partyAcctgPreference |
| //this is sequential sequencing, we can't skip a number, also it must be a unique sequence per partyIdFrom |
| |
| Timestamp nowTimestamp = UtilDateTime.nowTimestamp() |
| if (partyAcctgPreference.lastInvoiceRestartDate) { |
| //first figure out if we need to reset the lastInvoiceNumber; is the lastInvoiceRestartDate after the fiscalYearStartMonth/Day for this year? |
| curYearFiscalStartDate = UtilDateTime.getYearStart(nowTimestamp, |
| partyAcctgPreference.fiscalYearStartDay, partyAcctgPreference.fiscalYearStartMonth, 0L) |
| if (partyAcctgPreference.lastInvoiceRestartDate < curYearFiscalStartDate && nowTimestamp >= curYearFiscalStartDate) { |
| //less than fiscal year start, we need to reset it |
| partyAcctgPreference.lastInvoiceNumber = 1L |
| partyAcctgPreference.lastInvoiceRestartDate = nowTimestamp |
| } else { |
| //greater than or equal to fiscal year start or nowTimestamp hasn't yet hit the current year fiscal start date, we're okay, just increment |
| partyAcctgPreference.lastInvoiceNumber += 1L |
| } |
| } else { |
| //if no lastInvoiceRestartDate then it's easy, just start now with 1 |
| partyAcctgPreference.lastInvoiceNumber = 1L |
| partyAcctgPreference.lastInvoiceRestartDate = nowTimestamp |
| } |
| delegator.store(partyAcctgPreference) |
| |
| //get the current year string for prefix, etc; simple 4 digit year date string (using system defaults) |
| Integer curYearString = UtilDateTime.getYear(partyAcctgPreference.lastInvoiceRestartDate, timeZone, locale) |
| return success(invoiceId: "${curYearString}-${partyAcctgPreference.lastInvoiceNumber}") |
| } |
| |
| /** |
| * Create a new Invoice |
| * @return Success response containing the invoiceId, error response otherwise. |
| */ |
| Map createInvoice() { |
| if (!parameters.invoiceId) { |
| Map serviceResult = run service: 'getNextInvoiceId', with: [*: parameters, |
| partyId: parameters.partyIdFrom] |
| parameters.invoiceId = serviceResult.invoiceId |
| } |
| GenericValue party = from('Party').where(parameters).queryOne() |
| if (party?.preferredCurrencyUomId) { |
| parameters.currencyUomId = party.preferredCurrencyUomId |
| } |
| GenericValue invoice = makeValue('Invoice', parameters) |
| invoice.create() |
| run service: 'createInvoiceStatus', with: parameters |
| return success([invoiceId: invoice.invoiceId]) |
| } |
| |
| /** |
| * Retrieve an invoice and the items |
| * @return Success response containing the invoice and items, failure response otherwise. |
| */ |
| Map getInvoice() { |
| GenericValue invoice = from('Invoice').where(parameters).queryOne() |
| if (!invoice) { |
| return failure(label('AccountingUiLabels', 'AccountingInvoiceNotFound', parameters)) |
| } |
| return success([invoice: invoice, |
| invoiceItems: invoice.getRelated('InvoiceItem', null, null, false)]) |
| } |
| |
| /** |
| * Update the header of an existing Invoice |
| * @return Success response if invoice updated, error response otherwise. |
| */ |
| Map updateInvoice() { |
| GenericValue invoice = from('Invoice').where(parameters).queryOne() |
| if (!invoice) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceNotFound', parameters)) |
| } |
| if (invoice.statusId != 'INVOICE_IN_PROCESS') { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceUpdateOnlyWithInProcessStatus', [statustId: invoice.statusId])) |
| } |
| |
| // only save if something has changed, do not update status here |
| // update all non status and key fields |
| GenericValue lookedInvoice = invoice.clone() |
| invoice.setNonPKFields([*: parameters, statustId: 'INVOICE_IN_PROCESS'], false) |
| if (lookedInvoice != invoice) { |
| invoice.store() |
| } |
| |
| // check if there is a requested status change if yes call invoice status update service |
| if (parameters.statusId && parameters.statusId != 'INVOICE_IN_PROCESS') { |
| run service: 'setInvoiceStatus', with: [invoiceId: invoice.invoiceId, |
| statustId: parameters.statustId] |
| } |
| return success() |
| } |
| |
| /** |
| * Create a new Invoice from an existing invoice |
| * @return Success response containing the invoiceId, error response otherwise. |
| */ |
| Map copyInvoice() { |
| Map serviceResult = run service: 'getInvoice', with: [invoiceId: parameters.invoiceIdToCopyFrom] |
| GenericValue invoice = serviceResult.invoice |
| List<GenericValue> invoiceItems = serviceResult.invoiceItems |
| invoice.invoiceTypeId = parameters.invoiceTypeId ?: invoice.invoiceTypeId |
| serviceResult = run service: 'createInvoice', with: [*: invoice.getAllFields(), |
| invoiceId: null] |
| String newInvoiceId = serviceResult.invoiceId |
| invoiceItems.each { |
| run service: 'createInvoiceItem', with: [*: it.getAllFields(), |
| invoiceId: newInvoiceId] |
| } |
| return success([invoiceId: newInvoiceId]) |
| } |
| |
| /** |
| * Copy a invoice to a InvoiceType starting with 'template' |
| * @return Success response containing the invoiceId, error response otherwise. |
| */ |
| Map copyInvoiceToTemplate() { |
| String invoiceTypeId = parameters.invoiceTypeId |
| Map switchType = [SALES_INVOICE: 'SALES_INV_TEMPLATE', |
| PURCHASE_INVOICE: 'PUR_INV_TEMPLATE'] |
| run service: 'copyInvoice', with: [*: parameters, |
| invoiceIdToCopyFrom: parameters.invoiceId, |
| invoiceTypeId: switchType.get(invoiceTypeId) ?: invoiceTypeId] |
| } |
| |
| /** |
| * Set The Invoice Status |
| * @return Success response after status stored, error response otherwise. |
| */ |
| Map setInvoiceStatus() { |
| GenericValue invoice = from('Invoice').where(parameters).queryOne() |
| if (!invoice) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceNotFound', parameters)) |
| } |
| String oldStatusId = invoice.statusId |
| String invoiceTypeId = invoice.invoiceTypeId |
| Map returnResult = [oldStatusId: oldStatusId, invoiceTypeId: invoiceTypeId] |
| if (oldStatusId == parameters.statusId) { |
| return success(returnResult) |
| } |
| |
| if (from('StatusValidChange') |
| .where(statusId: oldStatusId, statusIdTo: parameters.statusId) |
| .queryCount() == 0) { |
| return error(label('AccountingUiLabels', 'AccountingPSInvalidStatusChange')) |
| } |
| |
| // if new status is paid check if the complete invoice is applied |
| if (parameters.statusId == 'INVOICE_PAID') { |
| BigDecimal notApplied = InvoiceWorker.getInvoiceNotApplied(invoice) |
| if (notApplied != 0) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceCannotChangeStatusToPaid')) |
| } |
| } |
| |
| // if it's OK to mark invoice paid, use parameters for paidDate |
| invoice.paidDate = parameters.paidDate ?: UtilDateTime.nowTimestamp() |
| |
| if (parameters.statusId == 'INVOICE_READY' && invoice.paidDate) { |
| invoice.paidDate = null |
| } |
| invoice.statusId = parameters.statusId |
| invoice.store() |
| |
| run service: 'createInvoiceStatus', with: [invoiceId: invoice.invoiceId, |
| statusId: invoice.statusId, |
| statusDate: parameters.statusDate] |
| |
| // if the invoice is a payrol invoice, create the payment in the not-paid status |
| // TODO the next part need to move on dedicate service |
| if (invoiceTypeId == 'PAYROL_INVOICE' && |
| ['INVOICE_APPROVED', 'INVOICE_READY'].contains(invoice.statusId)) { |
| // only generate payment if no application exist yet |
| List paymentApplications = invoice.getRelated('PaymentApplication', null, null, false) |
| if (!paymentApplications) { |
| BigDecimal amount = InvoiceWorker.getInvoiceTotal(invoice) |
| Map serviceResult = run service: 'createPayment', with: [partyIdFrom: invoice.partyId, |
| partyIdTo: invoice.partyIdFrom, |
| paymentMethodTypeId: 'COMPANY_CHECK', |
| paymentTypeId: 'PAYROL_PAYMENT', |
| statusId: 'PMNT_NOT_PAID', |
| currencyUomId: invoice.currencyUomId, |
| amount: amount] |
| run service: 'createPaymentApplication', with: [invoiceId: invoice.invoiceId, |
| paymentId: serviceResult.paymentId, |
| amountApplied: amount] |
| } |
| } |
| return success(returnResult) |
| } |
| |
| /** |
| * Check if the invoiceStatus is in progress |
| * @return Success response containing hasPermission to edit, error response otherwise. |
| */ |
| Map checkInvoiceStatusInProgress() { |
| GenericValue invoice = from('Invoice').where(parameters).cache().queryOne() |
| boolean hasPermission = invoice && invoice.statusId == 'INVOICE_IN_PROCESS' |
| return success([hasPermission: hasPermission]) |
| } |
| |
| /** |
| * Service run after cancel an invoice |
| * @return Success response containing the invoiceTypeId cancelled, error response otherwise. |
| */ |
| Map cancelInvoice() { |
| GenericValue invoice = from('Invoice').where(parameters).cache().queryOne() |
| if (!invoice) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceNotFound', parameters)) |
| } |
| invoice.getRelated('PaymentApplication', null, null, false).each { |
| GenericValue payment = it.getRelatedOne('Payment', false) |
| if (payment.statusId == 'PMNT_CONFIRMED') { |
| run service: 'setPaymentStatus', with: [paymentId: payment.paymentId, |
| statusId: UtilAccounting.isReceipt(payment) ? 'PMNT_RECEIVED' : 'PMNT_SENT'] |
| } |
| run service: 'removePaymentApplication', with: [paymentApplicationId: it.paymentApplicationId] |
| } |
| return success([invoiceTypeId: invoice.invoiceTypeId]) |
| } |
| |
| /** |
| * Send an invoice per Email |
| * @return Success response |
| */ |
| Map sendInvoicePerEmail() { |
| Map emailParams = dispatcher.getDispatchContext() |
| .makeValidContext([*: parameters, |
| xslfoAttachScreenLocation: 'component://accounting/widget/AccountingPrintScreens.xml#InvoicePDF', |
| bodyParameters: [invoiceId: parameters.invoiceId, |
| userLogin: parameters.userLogin, |
| other: parameters.other] //to print in 'other currency' |
| ]) |
| dispatcher.runAsync('sendMailFromScreen', emailParams) |
| return success(label('AccountingUiLabels', 'AccountingEmailScheduledToSend')) |
| } |
| |
| /** |
| * Create a new Invoice Item |
| * @return Success response containing the invoiceItemSeqId, error response otherwise. |
| */ |
| Map createInvoiceItem() { |
| GenericValue invoiceItem = makeValue('InvoiceItem', parameters) |
| if (!invoiceItem.invoiceItemSeqId) { |
| delegator.setNextSubSeqId(invoiceItem, 'invoiceItemSeqId', 5, 1) |
| } |
| // if there is no amount and a productItem is supplied fill the amount(price) and description from the product record |
| // TODO: there are return adjustments now that make this code very broken. The check for price was added as a quick fix. |
| if (invoiceItem.productId) { |
| invoiceItem.quantity = invoiceItem.quantity ?: 1 |
| if (!invoiceItem.amount) { |
| GenericValue product = from('Product').where(parameters).cache().queryOne() |
| invoiceItem.description = product.description |
| Map serviceResult = run service: 'calculateProductPrice', with: [product: product] |
| invoiceItem.amount = serviceResult.price |
| } |
| } |
| if (invoiceItem.amount == null) { // accept 0 |
| return error(label('AccountingUiLabels', 'AccountingInvoiceAmountIsMandatory')) |
| } |
| invoiceItem.create() |
| return success([invoiceId: invoiceItem.invoiceId, |
| invoiceItemSeqId: invoiceItem.invoiceItemSeqId]) |
| } |
| |
| /** |
| * Update an existing Invoice Item |
| * @return Success response after updated, error response otherwise. |
| */ |
| Map updateInvoiceItem() { |
| GenericValue invoiceItem = from('InvoiceItem').where(parameters).queryOne() |
| if (!invoiceItem) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceItemNotFound', parameters)) |
| } |
| GenericValue lookedInvoiceItem = invoiceItem.clone() |
| invoiceItem.setNonPKFields(parameters, false) |
| |
| // check if the productNumber is updated, when yes retrieve product description and price |
| if (lookedInvoiceItem.productId != invoiceItem.productId) { |
| GenericValue product = from('Product').where(parameters).cache().queryOne() |
| invoiceItem.description = product.description |
| Map serviceResult = run service: 'calculateProductPrice', with: [product: product] |
| invoiceItem.amount = serviceResult.price |
| if (invoiceItem.amount == null) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceAmountIsMandatory')) |
| } |
| } |
| if (lookedInvoiceItem != invoiceItem) { |
| invoiceItem.store() |
| } |
| return success([invoiceId: invoiceItem.invoiceId, |
| invoiceItemSeqId: invoiceItem.invoiceItemSeqId]) |
| } |
| |
| /** |
| * Remove an existing Invoice Item |
| * @return Success response after remove, error response otherwise. |
| */ |
| Map removeInvoiceItem() { |
| GenericValue invoiceItem = from('InvoiceItem').where(parameters).queryOne() |
| if (!invoiceItem) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceItemNotFound', parameters)) |
| } |
| // check if there are specific item paymentApplications when yes remove those |
| invoiceItem.removeRelated('PaymentApplication') |
| invoiceItem.remove() |
| return success() |
| } |
| |
| /** |
| * Scheduled service to generate Invoice from an existing Invoice |
| */ |
| Map autoGenerateInvoiceFromExistingInvoice() { |
| Map switchType = [SALES_INV_TEMPLATE: 'SALES_INVOICE', |
| PUR_INV_TEMPLATE: 'PURCHASE_INVOICE'] |
| from('Invoice') |
| .where(recurrenceInfoId: parameters.recurrenceInfoId) |
| .queryList() |
| .each { |
| Map serviceResult = run service: 'copyInvoice', with: [*: it.getAllFields(), |
| invoiceIdToCopyFrom: it.invoiceId] |
| if (switchType.containsKey(it.invoiceTypeId)) { |
| String invoiceId = serviceResult.invoiceId |
| run service: 'updateInvoice', with: [invoiceId: invoiceId, |
| recurrenceInfoId: null, |
| invoiceTypeId: switchType(it.invoiceTypeId)] |
| } |
| } |
| return success() |
| } |
| |
| /** |
| * Calculate running total for Invoices |
| * @return Success response containing the invoiceRunningTotal, error response otherwise. |
| */ |
| Map getInvoiceRunningTotal() { |
| BigDecimal runningTotal = 0 |
| parameters.invoiceIds.each { |
| Map serviceResult = run service: 'getInvoicePaymentInfoList', with: [invoiceId: it] |
| if (serviceResult.invoicePaymentInfoList) { |
| runningTotal += serviceResult.invoicePaymentInfoList[0].outstandingAmount |
| } |
| } |
| Map serviceResult = run service: 'getPartyAccountingPreferences', with: parameters |
| Map partyAccountingPreference = serviceResult.partyAccountingPreference |
| String currencyUomId = partyAccountingPreference.baseCurrencyUomId ?: |
| EntityUtilProperties.getPropertyValue('general', 'currency.uom.id.default', 'USD', delegator) |
| return success([invoiceRunningTotal: UtilFormatOut.formatCurrency(runningTotal, currencyUomId, parameters.locale)]) |
| } |
| |
| /** |
| * Filter invoices by invoiceItemAssocTypeId |
| * @return Success response containing filteredInvoiceList, error response otherwise. |
| */ |
| Map getInvoicesFilterByAssocType() { |
| EntityCondition condition = new EntityConditionBuilder().AND { |
| EQUALS(invoiceItemAssocTypeId: parameters.invoiceItemAssocTypeId) |
| IN(invoiceIdFrom: parameters.invoiceList*.invoiceId) |
| } |
| List invoiceIds = from('InvoiceItemAssoc') |
| .where(condition) |
| .distinct() |
| .filterByDate() |
| .getFieldList('invoiceIdFrom') |
| return success([filteredInvoiceList: parameters.invoiceList.findAll { invoiceIds.contains(it.invoiceId) }]) |
| } |
| |
| /** |
| * Remove invoiceItemAssoc record on cancel invoice |
| * @return Success response after remove, error response otherwise. |
| */ |
| Map removeInvoiceItemAssocOnCancelInvoice() { |
| from('InvoiceItemAssoc') |
| .where(invoiceIdTo: parameters.invoiceId) |
| .queryList() |
| .each { |
| run service: 'deleteInvoiceItemAssoc', with: it.getAllFields() |
| } |
| return success() |
| } |
| |
| /** |
| * Reset OrderItemBilling and OrderAdjustmentBilling records on cancel invoice, |
| * so it is isn't considered invoiced any more by createInvoiceForOrder service |
| * @return Success response |
| */ |
| Map resetOrderItemBillingAndOrderAdjustmentBillingOnCancelInvoice() { |
| from('OrderItemBilling') |
| .where(invoiceId: parameters.invoiceId) |
| .queryList() |
| .each { |
| it.quantity = 0 |
| it.store() |
| } |
| from('OrderAdjustmentBilling') |
| .where(invoiceId: parameters.invoiceId) |
| .queryList() |
| .each { |
| it.amount = 0 |
| it.store() |
| } |
| return success() |
| } |
| |
| /** |
| * Service set status of Invoices in bulk. |
| * @return Success response |
| */ |
| Map massChangeInvoiceStatus() { |
| parameters.invoiceIds.each { |
| run service: 'setInvoiceStatus', with: [invoiceId: it, |
| statusId: parameters.statusId] |
| } |
| return success() |
| } |
| |
| /** |
| * Set Parameter And Call Tax Calculate Service |
| * @return Success response |
| */ |
| Map addTaxOnInvoice() { |
| GenericValue invoice = from('Invoice').where(parameters).cache().queryOne() |
| if (!invoice) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceNotFound', parameters)) |
| } |
| GenericValue shippingContact = from('PartyContactMechPurpose') |
| .where(partyId: invoice.partyId, |
| contactMechPurposeTypeId: 'SHIPPING_LOCATION') |
| .queryFirst() ?: |
| from('PartyContactMechPurpose') |
| .where(partyId: invoice.partyId, |
| contactMechPurposeTypeId: 'GENERAL_LOCATION') |
| .queryFirst() |
| if (!shippingContact) { |
| return error(label('AccountingUiLabels', 'AccountingTaxCannotCalculate')) |
| } |
| GenericValue postalAddress = from('PostalAddress').where(contactMechId: shippingContact.contactMechId).cache().queryOne() |
| Map addTaxMap = [billToPartyId: invoice.invoiceTypeId == 'SALES_INVOICE' ? invoice.partyId : invoice.partyIdFrom, |
| payToPartyId: invoice.partyIdFrom, |
| orderPromotionsAmount: 0, |
| orderShippingAmount: 0, |
| shippingAddress: postalAddress, |
| itemProductList: [], |
| itemAmountList: [], |
| itemPriceList: [], |
| itemQuantityList: [], |
| itemShippingList: []] |
| List invoiceItems = invoice.getRelated('InvoiceItem', null, null, false) |
| invoiceItems.each { |
| BigDecimal totalAmount = 0 |
| if (it.productId) { |
| addTaxMap.itemProductList << from('Product').where(productId: it.productId).cache().queryOne() |
| List promoAdjs = EntityUtil.filterByAnd(invoiceItems, |
| [productId: it.productId, |
| invoiceItemTypeId: 'ITM_PROMOTION_ADJ']) |
| totalAmount = it.amount * it.quantity |
| if (promoAdjs) { |
| totalAmount -= it.amount |
| } |
| } |
| addTaxMap.itemAmountList << totalAmount |
| addTaxMap.itemPriceList << it.amount |
| addTaxMap.itemQuantityList << it.quantity |
| addTaxMap.itemShippingList << 0 |
| } |
| if (!addTaxMap.itemProductList) { |
| return error(label('AccountingUiLabels', 'AccountingTaxProductIdCannotCalculate')) |
| } |
| Map serviceResult = run service: 'calcTax', with: addTaxMap |
| Map itemMap = [itemSeqIdList: [], |
| productList: []] |
| invoiceItems.findAll { it.productId }.each { |
| itemMap.itemSeqIdList << it.invoiceItemSeqId |
| itemMap.productList << it.productId |
| } |
| Long countItemId = -1 |
| serviceResult.itemAdjustments.each { |
| countItemId ++ |
| if (it) { |
| it.each { |
| run service: 'createInvoiceItem', with: [invoiceItemTypeId: invoice.invoiceTypeId == 'PURCHASE_INVOICE' ? |
| 'PITM_SALES_TAX' : 'ITM_SALES_TAX', |
| invoiceId: invoice.invoiceId, |
| overrideGlAccountId: it.overrideGlAccountId, |
| productId: itemMap.productList[countItemId], |
| taxAuthPartyId: it.taxAuthPartyId, |
| taxAuthGeoId: it.taxAuthGeoId, |
| amount: it.amount, |
| quantity: 1, |
| parentInvoiceItemSeqId: itemMap.itemSeqIdList[countItemId], |
| taxAuthorityRateSeqId: it.taxAuthorityRateSeqId, |
| description: it.comments] |
| } |
| } |
| } |
| serviceResult.orderAdjustments.each { |
| run service: 'createInvoiceItem', with: [invoiceItemTypeId: invoice.invoiceTypeId == 'PURCHASE_INVOICE' ? |
| 'PITM_SALES_TAX' : 'ITM_SALES_TAX', |
| invoiceId: invoice.invoiceId, |
| overrideGlAccountId: it.overrideGlAccountId, |
| taxAuthPartyId: it.taxAuthPartyId, |
| taxAuthGeoId: it.taxAuthGeoId, |
| amount: it.amount, |
| quantity: 1, |
| taxAuthorityRateSeqId: it.taxAuthorityRateSeqId, |
| description: it.comments] |
| } |
| return success() |
| } |
| |
| /** |
| * Create an invoice from existing order when invoicePerShipment is N |
| * @return Success response |
| */ |
| Map createInvoiceFromOrder() { |
| GenericValue order = from('OrderHeader').where(parameters).queryOne() |
| String invoicePerShipment = order?.invoicePerShipment ?: |
| EntityUtilProperties.getPropertyValue('accounting', 'create.invoice.per.shipment', 'N', delegator) |
| if (invoicePerShipment == 'N') { |
| List orderItemBillingItemsSeqIds = from('OrderItemBilling').where(orderId: order.orderId).getFieldList('orderItemSeqId') |
| if (!orderItemBillingItemsSeqIds) { |
| Map serviceResult = run service: 'createInvoiceForOrderAllItems', with: [orderId: order.orderId] |
| return serviceResult |
| } |
| List orderItems = from('OrderItem').where(orderId: order.orderId).queryList() |
| orderItems = orderItems.findAll { !orderItemBillingItemsSeqIds.contains(it.orderItemSeqId) } |
| Map serviceResult = run service: 'createInvoiceForOrder', with: [orderId: order.orderId, |
| billItems: orderItems] |
| return serviceResult |
| } |
| return success() |
| } |
| |
| /** |
| * check if a invoice is in a foreign currency related to the accounting company. |
| * @return Success response |
| */ |
| Map isInvoiceInForeignCurrency() { |
| GenericValue invoice = from('Invoice').where(parameters).cache().queryOne() |
| if (!invoice) { |
| return error(label('AccountingUiLabels', 'AccountingInvoiceNotFound', parameters)) |
| } |
| String partyId = EntityTypeUtil.hasParentType(delegator, 'InvoiceType', 'invoiceTypeId', |
| invoice.invoiceTypeId, 'parentTypeId', 'PURCHASE_INVOICE') ? |
| invoice.partyId : invoice.partyIdFrom |
| Map serviceResult = run service: 'getPartyAccountingPreferences', with: [organizationPartyId: partyId] |
| return success([isForeign: invoice.currencyUomId == serviceResult.baseCurrencyUomId]) |
| } |