KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > opensourcestrategies > financials > ledger > LedgerServices


1 /*
2  * Copyright (c) 2006 - 2007 Open Source Strategies, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the Honest Public License.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * Honest Public License for more details.
11  *
12  * You should have received a copy of the Honest Public License
13  * along with this program; if not, write to Funambol,
14  * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
15  */

16
17 package com.opensourcestrategies.financials.ledger;
18
19 import java.sql.Timestamp JavaDoc;
20 import java.util.*;
21 import java.math.BigDecimal JavaDoc;
22
23 import org.ofbiz.accounting.invoice.InvoiceWorker;
24 import org.ofbiz.accounting.payment.PaymentWorker;
25 import org.ofbiz.accounting.util.UtilAccounting;
26 import org.ofbiz.accounting.AccountingException;
27 import org.ofbiz.accounting.invoice.InvoiceWorker;
28 import org.ofbiz.base.util.*;
29 import org.ofbiz.entity.GenericDelegator;
30 import org.ofbiz.entity.GenericEntityException;
31 import org.ofbiz.entity.GenericValue;
32 import org.ofbiz.entity.condition.EntityCondition;
33 import org.ofbiz.entity.condition.EntityConditionList;
34 import org.ofbiz.entity.condition.EntityExpr;
35 import org.ofbiz.entity.condition.EntityOperator;
36 import org.ofbiz.entity.util.EntityUtil;
37 import org.ofbiz.service.DispatchContext;
38 import org.ofbiz.service.GenericServiceException;
39 import org.ofbiz.service.LocalDispatcher;
40 import org.ofbiz.service.ModelService;
41 import org.ofbiz.service.ServiceUtil;
42
43 import com.opensourcestrategies.financials.util.UtilCOGS;
44 import com.opensourcestrategies.financials.util.UtilFinancial;
45
46 /**
47  * LedgerServices - Services for posting transactions to the general ledger
48  *
49  * @author <a HREF="mailto:sichen@opensourcestrategies.com">Si Chen</a>
50  * @version $Rev: 553 $
51  * @since 2.2
52  */

53
54
55 public class LedgerServices {
56
57     public static final String JavaDoc module = LedgerServices.class.getName();
58     public static final String JavaDoc INVOICE_PRODUCT_ITEM_TYPE = "INV_FPROD_ITEM"; // invoiceTypeId for invoice items which are products
59
public static final String JavaDoc PURCHINV_PRODUCT_ITEM_TYPE = "PINV_FPROD_ITEM"; // invoiceTypeId for invoice items which are products
60
public static final String JavaDoc RETINV_PRODUCT_ITEM_TYPE = "RINV_FPROD_ITEM"; // invoiceTypeId for invoice items which are products
61
public static final String JavaDoc resource = "FinancialsUiLabels";
62
63     // TODO: replace code that uses epsilon with BigDecimal and also set BigDecimal config in some common class/properties file
64
public static final double epsilon = 0.000001; // smallest allowable rounding error in accounting transactions
65

66     private static BigDecimal JavaDoc ZERO = new BigDecimal JavaDoc("0");
67     private static int decimals = -1;
68     private static int rounding = -1;
69     static {
70         decimals = UtilNumber.getBigDecimalScale("invoice.decimals");
71         rounding = UtilNumber.getBigDecimalRoundingMode("invoice.rounding");
72         // set zero to the proper scale
73
ZERO.setScale(decimals);
74     }
75
76     public static Map postInvoiceToGl(DispatchContext dctx, Map context) {
77         GenericDelegator delegator = dctx.getDelegator();
78         LocalDispatcher dispatcher = dctx.getDispatcher();
79         GenericValue userLogin = (GenericValue) context.get("userLogin");
80
81         String JavaDoc invoiceId = (String JavaDoc) context.get("invoiceId");
82         try {
83
84             // get the invoice and make sure it has an invoiceTypeId
85
GenericValue invoice = delegator.findByPrimaryKeyCache("Invoice", UtilMisc.toMap("invoiceId", invoiceId));
86             if (invoice == null) {
87                 return ServiceUtil.returnError("No invoice found for invoiceId of " + invoiceId);
88             } else if (invoice.get("invoiceTypeId") == null) {
89                 return ServiceUtil.returnError("Invoice " + invoiceId + " has a null invoice type and cannot be processed");
90             }
91             String JavaDoc invoiceTypeId = (String JavaDoc) invoice.get("invoiceTypeId");
92
93             // accounting data
94
String JavaDoc transactionPartyId = null;
95             String JavaDoc transactionPartyRoleTypeId = null;
96             String JavaDoc acctgTransTypeId = null;
97             String JavaDoc organizationPartyId = null;
98             String JavaDoc offsettingGlAccountTypeId = null;
99             String JavaDoc defaultDebitCreditFlag = null;
100             String JavaDoc offsettingDebitCreditFlag = null;
101             String JavaDoc defaultGlAccountTypeId = null;
102             
103             // set accounting data according to invoiceTypeId
104
if ("SALES_INVOICE".equals(invoiceTypeId)) {
105                 acctgTransTypeId = "SALES_ACCTG_TRANS";
106                 offsettingGlAccountTypeId = "ACCOUNTS_RECEIVABLE";
107                 organizationPartyId = invoice.getString("partyIdFrom");
108                 transactionPartyId = invoice.getString("partyId");
109                 transactionPartyRoleTypeId = "BILL_TO_CUSTOMER";
110                 defaultDebitCreditFlag = "C";
111                 offsettingDebitCreditFlag = "D";
112                 defaultGlAccountTypeId = "SALES_ACCOUNT";
113             } else if ("PURCHASE_INVOICE".equals(invoiceTypeId)) {
114                 acctgTransTypeId = "OBLIGATION_ACCTG_TRA";
115                 offsettingGlAccountTypeId = "ACCOUNTS_PAYABLE";
116                 organizationPartyId = invoice.getString("partyId");
117                 transactionPartyId = invoice.getString("partyIdFrom");
118                 transactionPartyRoleTypeId = "BILL_FROM_VENDOR";
119                 defaultDebitCreditFlag = "D";
120                 offsettingDebitCreditFlag = "C";
121                 defaultGlAccountTypeId = "UNINVOICED_SHIP_RCPT";
122             } else if ("CUST_RTN_INVOICE".equals(invoiceTypeId)) {
123                 acctgTransTypeId = "OBLIGATION_ACCTG_TRA";
124                 offsettingGlAccountTypeId = "CUSTOMER_CREDIT";
125                 organizationPartyId = invoice.getString("partyId");
126                 transactionPartyId = invoice.getString("partyIdFrom");
127                 transactionPartyRoleTypeId = "BILL_TO_CUSTOMER";
128                 defaultDebitCreditFlag = "D";
129                 offsettingDebitCreditFlag = "C";
130                 defaultGlAccountTypeId = "SALES_RETURN";
131             } else {
132                 Debug.logWarning("Invoice [" + invoiceId + "] has an unsupported invoice type of [" + invoiceTypeId + "] and was not posted to the ledger", module);
133                 return ServiceUtil.returnSuccess();
134             }
135
136             // invoke helper method to process invoice items
137
Map tmpResult = processInvoiceItems(
138                     delegator, dispatcher, userLogin, invoice,
139                     acctgTransTypeId, offsettingGlAccountTypeId,
140                     organizationPartyId, transactionPartyId, transactionPartyRoleTypeId,
141                     defaultDebitCreditFlag, defaultGlAccountTypeId
142                     );
143
144             // return on any service errors
145
if (ServiceUtil.isError(tmpResult)) return tmpResult;
146
147             // create a list of AcctgTransEntries based on processInvoiceItems
148
int itemSeqId = 1;
149             List acctgTransEntries = new ArrayList();
150             List acctgTransEntryMaps = (List) tmpResult.get("acctgTransEntries");
151             for (Iterator iter = acctgTransEntryMaps.iterator(); iter.hasNext(); ) {
152                 Map input = (Map) iter.next();
153
154                 // skip those with amount 0
155
if (((Double JavaDoc) input.get("amount")).doubleValue() == 0.0) {
156                     continue;
157                 }
158
159                 // make the next transaction entry
160
input.put("acctgTransEntrySeqId", Integer.toString(itemSeqId));
161                 acctgTransEntries.add(delegator.makeValue("AcctgTransEntry", input));
162                 itemSeqId++;
163
164                 if (Debug.verboseOn()) {
165                     Debug.logVerbose(acctgTransEntries.get(acctgTransEntries.size()-1).toString(), module);
166                 }
167             }
168
169             // create the offsetting GL transaction and add to list
170
Map input = new HashMap();
171             input.put("glAccountId", tmpResult.get("offsettingGlAccountId"));
172             input.put("acctgTransEntrySeqId", Integer.toString(itemSeqId));
173             input.put("organizationPartyId", organizationPartyId);
174             input.put("partyId", transactionPartyId);
175             input.put("roleTypeId", transactionPartyRoleTypeId);
176             input.put("debitCreditFlag", offsettingDebitCreditFlag);
177             input.put("amount", tmpResult.get("postingTotal"));
178             input.put("acctgTransEntryTypeId", "_NA_");
179             Debug.logInfo(input.toString(), module);
180             GenericValue offsettingAcctgTransEntry = delegator.makeValue("AcctgTransEntry", input);
181             acctgTransEntries.add(offsettingAcctgTransEntry);
182
183             // now use createAcctgTransAndEntries to create an accounting transaction for the invoice
184
input = new HashMap();
185             input.put("acctgTransEntries", acctgTransEntries);
186             input.put("invoiceId", invoiceId);
187             input.put("partyId", transactionPartyId);
188             input.put("roleTypeId", transactionPartyRoleTypeId);
189             input.put("glFiscalTypeId", "ACTUAL");
190             input.put("acctgTransTypeId", acctgTransTypeId);
191             input.put("userLogin", userLogin);
192             tmpResult = dispatcher.runSync("createAcctgTransAndEntries", input);
193             
194             // on success, return the acctgTransId
195
if (((String JavaDoc) tmpResult.get(ModelService.RESPONSE_MESSAGE)).equals(ModelService.RESPOND_SUCCESS)) {
196                 Map result = ServiceUtil.returnSuccess();
197                 result.put("acctgTransId", tmpResult.get("acctgTransId"));
198                 return(result);
199             }
200             
201             // otherwise we have an error, which we pass up
202
return tmpResult;
203         } catch (GenericEntityException ee) {
204             return ServiceUtil.returnError(ee.getMessage());
205         } catch (GenericServiceException se) {
206             return ServiceUtil.returnError(se.getMessage());
207         }
208     }
209
210     public static Map postInvoiceWriteoffToGl(DispatchContext dctx, Map context) {
211         GenericDelegator delegator = dctx.getDelegator();
212         LocalDispatcher dispatcher = dctx.getDispatcher();
213         GenericValue userLogin = (GenericValue) context.get("userLogin");
214         Map success = ServiceUtil.returnSuccess();
215         Locale locale = (Locale) context.get("locale");
216
217         String JavaDoc invoiceId = (String JavaDoc) context.get("invoiceId");
218         
219         try {
220
221             // Get the Invoice and make sure it has an invoiceTypeId
222
GenericValue invoice = delegator.findByPrimaryKeyCache("Invoice", UtilMisc.toMap("invoiceId", invoiceId));
223             if (invoice == null) {
224                 return ServiceUtil.returnError("No invoice found for invoiceId of " + invoiceId);
225             } else if (invoice.get("invoiceTypeId") == null) {
226                 return ServiceUtil.returnError("Invoice " + invoiceId + " has a null invoice type and cannot be processed");
227             }
228             String JavaDoc invoiceTypeId = (String JavaDoc) invoice.get("invoiceTypeId");
229             BigDecimal JavaDoc invoiceUnappliedAmount = InvoiceWorker.getInvoiceNotApplied(invoice);
230             if (invoiceUnappliedAmount.signum() != 1) {
231                 return success;
232             }
233
234             // Set accounting data according to invoiceTypeId
235
String JavaDoc organizationPartyId = null;
236             String JavaDoc transactionPartyId = null;
237             String JavaDoc creditGlAccountTypeId = null;
238             String JavaDoc debitGlAccountTypeId = null;
239             if ("SALES_INVOICE".equals(invoiceTypeId)) {
240                 organizationPartyId = invoice.getString("partyIdFrom");
241                 transactionPartyId = invoice.getString("partyId");
242                 creditGlAccountTypeId = "ACCOUNTS_RECEIVABLE";
243                 debitGlAccountTypeId = "ACCTRECV_WRITEOFF";
244             } else if ("PURCHASE_INVOICE".equals(invoiceTypeId)) {
245                 organizationPartyId = invoice.getString("partyId");
246                 transactionPartyId = invoice.getString("partyIdFrom");
247                 creditGlAccountTypeId = "ACCTPAY_WRITEOFF";
248                 debitGlAccountTypeId = "ACCOUNTS_PAYABLE";
249             } else if ("COMMISSIONS_INVOICE".equals(invoiceTypeId)) {
250                 organizationPartyId = invoice.getString("partyIdFrom");
251                 transactionPartyId = invoice.getString("partyId");
252                 creditGlAccountTypeId = "COMMISSIONS_WRITEOFF";
253                 debitGlAccountTypeId = "COMMISSIONS_PAYABLE";
254             } else if ("INTEREST_INVOICE".equals(invoiceTypeId)) {
255                 organizationPartyId = invoice.getString("partyIdFrom");
256                 transactionPartyId = invoice.getString("partyId");
257                 creditGlAccountTypeId = "INTRSTINC_RECEIVABLE";
258                 debitGlAccountTypeId = "INTRSTINC_WRITEOFF";
259             } else {
260                 Debug.logWarning("Invoice [" + invoiceId + "] has an unsupported invoice type of [" + invoiceTypeId + "], and its write-off transaction was not posted to the ledger", module);
261                 return success;
262             }
263
264             // Attempt to retrieve the glAccountIds
265
List errorMessageList = new ArrayList();
266             String JavaDoc creditGlAccountId = null;
267             String JavaDoc debitGlAccountId = null;
268             try {
269                 creditGlAccountId = UtilAccounting.getProductOrgGlAccountId(null, creditGlAccountTypeId, organizationPartyId, delegator);
270             } catch( AccountingException e ) {
271                 errorMessageList.add(UtilProperties.getMessage(resource, "FinancialsServiceErrorNoGlAccountTypeDefaultFound", UtilMisc.toMap("organizationPartyId", organizationPartyId, "glAccountTypeId", creditGlAccountTypeId), locale));
272             }
273             try {
274                 debitGlAccountId = UtilAccounting.getProductOrgGlAccountId(null, debitGlAccountTypeId, organizationPartyId, delegator);
275             } catch( AccountingException e ) {
276                 errorMessageList.add(UtilProperties.getMessage(resource, "FinancialsServiceErrorNoGlAccountTypeDefaultFound", UtilMisc.toMap("organizationPartyId", organizationPartyId, "glAccountTypeId", debitGlAccountTypeId), locale));
277             }
278             if (errorMessageList.size() > 0) {
279                 return ServiceUtil.returnError(errorMessageList);
280             }
281
282             List acctgTransEntries = new ArrayList();
283
284             Map creditAcctgTransEntryItems = new HashMap();
285             creditAcctgTransEntryItems.put("acctgTransEntrySeqId", "1");
286             creditAcctgTransEntryItems.put("acctgTransEntryTypeId", "_NA_");
287             creditAcctgTransEntryItems.put("glAccountId", creditGlAccountId);
288             creditAcctgTransEntryItems.put("organizationPartyId", organizationPartyId);
289             creditAcctgTransEntryItems.put("partyId", transactionPartyId);
290             creditAcctgTransEntryItems.put("debitCreditFlag", "C");
291             creditAcctgTransEntryItems.put("amount", new Double JavaDoc(invoiceUnappliedAmount.doubleValue()));
292             creditAcctgTransEntryItems.put("currencyUomId", invoice.getString("currencyUomId"));
293             acctgTransEntries.add(delegator.makeValue("AcctgTransEntry", creditAcctgTransEntryItems));
294             
295             Map debitAcctgTransEntryItems = new HashMap();
296             debitAcctgTransEntryItems.put("acctgTransEntrySeqId", "2");
297             debitAcctgTransEntryItems.put("acctgTransEntryTypeId", "_NA_");
298             debitAcctgTransEntryItems.put("glAccountId", debitGlAccountId);
299             debitAcctgTransEntryItems.put("organizationPartyId", organizationPartyId);
300             debitAcctgTransEntryItems.put("partyId", transactionPartyId);
301             debitAcctgTransEntryItems.put("debitCreditFlag", "D");
302             debitAcctgTransEntryItems.put("amount", new Double JavaDoc(invoiceUnappliedAmount.doubleValue()));
303             debitAcctgTransEntryItems.put("currencyUomId", invoice.getString("currencyUomId"));
304             acctgTransEntries.add(delegator.makeValue("AcctgTransEntry", debitAcctgTransEntryItems));
305             
306             Map acctgTransItems = new HashMap();
307             acctgTransItems.put("acctgTransEntries", acctgTransEntries);
308             acctgTransItems.put("acctgTransTypeId", "WRITEOFF");
309             acctgTransItems.put("description", "Invoice written off");
310             acctgTransItems.put("invoiceId", invoiceId);
311             acctgTransItems.put("glFiscalTypeId", "ACTUAL");
312             acctgTransItems.put("userLogin", userLogin);
313             Map createAcctgTransAndEntriesResult = dispatcher.runSync("createAcctgTransAndEntries", acctgTransItems);
314             
315             if (ServiceUtil.isError(createAcctgTransAndEntriesResult) || ServiceUtil.isFailure(createAcctgTransAndEntriesResult)) {
316                 return createAcctgTransAndEntriesResult;
317             }
318             
319         } catch (GenericEntityException ee) {
320             return ServiceUtil.returnError(ee.getMessage());
321         } catch (GenericServiceException se) {
322             return ServiceUtil.returnError(se.getMessage());
323         }
324
325         return success;
326     }
327
328     /**
329      * Iterates through each InvoiceItem in an Invoice and creates a map based on
330      * AcctgTransEntry for each invoice item. Also determines the offsettingGlAccountId
331      * and keeps a running total for all the transactions. The general code for processing
332      * all invoice types is here, and special processing by invoice type for each InvoiceItem
333      * is handled through specialized methods. The result is a map that includes the
334      * offsettingGlAccountId, the postingTotal, and a list of transaction entry Maps that
335      * are ready to be turned into GenericValues.
336      */

337     private static Map processInvoiceItems(
338             GenericDelegator delegator, LocalDispatcher dispatcher, GenericValue userLogin,
339             GenericValue invoice, String JavaDoc acctgTransTypeId, String JavaDoc offsettingGlAccountTypeId,
340             String JavaDoc organizationPartyId, String JavaDoc transactionPartyId, String JavaDoc transactionPartyRoleTypeId,
341             String JavaDoc defaultDebitCreditFlag, String JavaDoc defaultGlAccountTypeId
342             )
343         throws GenericEntityException, GenericServiceException {
344
345         // get the offsetting gl account for this invoice
346
String JavaDoc offsettingGlAccountId = UtilAccounting.getDefaultAccountId(offsettingGlAccountTypeId, organizationPartyId, delegator);
347         Debug.logInfo("Posting to GL for party " + organizationPartyId + " and offsetting account " + offsettingGlAccountId, module);
348
349         // get the conversion factor for converting the invoice's amounts to the currencyUomId of the organization
350
BigDecimal JavaDoc conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, organizationPartyId, invoice.getString("currencyUomId")));
351
352         // loop variables
353
List acctgTransEntries = new ArrayList(); // a List of AcctgTransEntry entities to be posted to
354
BigDecimal JavaDoc postingTotal = new BigDecimal JavaDoc("0.00");
355         List invoiceItems = invoice.getRelatedCache("InvoiceItem", UtilMisc.toList("invoiceItemSeqId"));
356
357         // Now, for each line item of the invoice, figure out which account on GL to post to and add it to a List
358
Iterator invoiceItemIterator = invoiceItems.iterator();
359         while (invoiceItemIterator.hasNext()) {
360             GenericValue invoiceItem = (GenericValue) invoiceItemIterator.next();
361
362             // default null quantities to 1.0
363
if (invoiceItem.getDouble("quantity") == null) {
364                 invoiceItem.set("quantity", new Double JavaDoc(1.0));
365             }
366
367             BigDecimal JavaDoc amount = invoiceItem.getBigDecimal("amount");
368             BigDecimal JavaDoc quantity = invoiceItem.getBigDecimal("quantity");
369
370             // do not post invoice items with net zero value (usually because there's no amount)
371
if ((amount == null) || (amount.signum() == 0) || (quantity.signum() == 0)) {
372                 continue;
373             }
374
375             // GL account for posting this invoice item, default to overrideGlAccountId if present
376
String JavaDoc invoiceItemPostingGlAccountId = invoiceItem.getString("overrideGlAccountId");
377
378             if (invoiceItemPostingGlAccountId == null) {
379
380                 // if this invoice item is for a product, check to see if it has a particular GL account defined in ProductGlAccount,
381
// or, alternatively, in GlAccountTypeDefault
382
String JavaDoc invoiceItemTypeId = invoiceItem.getString("invoiceItemTypeId");
383                 if ((invoiceItemTypeId.equals(INVOICE_PRODUCT_ITEM_TYPE))
384                         || (invoiceItemTypeId.equals(PURCHINV_PRODUCT_ITEM_TYPE))
385                         || invoiceItemTypeId.equals(RETINV_PRODUCT_ITEM_TYPE)) {
386                     invoiceItemPostingGlAccountId = UtilAccounting.getProductOrgGlAccountId(invoiceItem.getString("productId"),
387                             defaultGlAccountTypeId, organizationPartyId, delegator);
388                 }
389             }
390
391             // specific invoice type processing for each invoice item
392
// note that these methods should add acctgTransEntries and return the postingTotal in a Map
393
Map tmpResult = null;
394             if ("SALES_INVOICE".equals(invoice.getString("invoiceTypeId"))) {
395                 tmpResult = processSalesInvoiceItem(
396                         delegator, dispatcher, userLogin,
397                         invoice, invoiceItem, acctgTransEntries,
398                         postingTotal, conversionFactor, amount, quantity,
399                         invoiceItemPostingGlAccountId, acctgTransTypeId, offsettingGlAccountTypeId,
400                         organizationPartyId, transactionPartyId, transactionPartyRoleTypeId,
401                         defaultDebitCreditFlag, defaultGlAccountTypeId
402                         );
403             } else { // for both purchase and return invoices, run processPurchaseInvoiceItem
404
tmpResult = processPurchaseInvoiceItem(
405                         delegator, dispatcher, userLogin,
406                         invoice, invoiceItem, acctgTransEntries,
407                         postingTotal, conversionFactor, amount, quantity,
408                         invoiceItemPostingGlAccountId, acctgTransTypeId, offsettingGlAccountTypeId,
409                         organizationPartyId, transactionPartyId, transactionPartyRoleTypeId,
410                         defaultDebitCreditFlag, defaultGlAccountTypeId
411                         );
412             }
413             if (ServiceUtil.isError(tmpResult)) return tmpResult;
414
415             if (Debug.verboseOn()) {
416                 Debug.logVerbose("invoiceItem " + invoiceItem.getString("invoiceId") + ", " + invoiceItem.getString("invoiceItemSeqId") + ": gl account = "
417                         + invoiceItemPostingGlAccountId + ", amount = " + invoiceItem.getDouble("amount") + ", quantity = "
418                         + invoiceItem.getDouble("quantity") + ", default debit/credit flag = " + defaultDebitCreditFlag, module);
419             }
420             postingTotal = (BigDecimal JavaDoc) tmpResult.get("postingTotal");
421         }
422         return UtilMisc.toMap("offsettingGlAccountId", offsettingGlAccountId, "acctgTransEntries", acctgTransEntries, "postingTotal", new Double JavaDoc(postingTotal.doubleValue()));
423     }
424
425     /**
426      * For purchase invoices, this method creates accounting transaction entries first
427      * for uninvoiced shipment receipts. Then it reconciles the aggregate value of all order items
428      * related to the invoice item with the actual value of the invoice item and posts it as a
429      * purchase price variance. Note that this method is also used for return invoices.
430      */

431     private static Map processPurchaseInvoiceItem(
432             GenericDelegator delegator, LocalDispatcher dispatcher, GenericValue userLogin,
433             GenericValue invoice, GenericValue invoiceItem, List acctgTransEntries,
434             BigDecimal JavaDoc postingTotal, BigDecimal JavaDoc conversionFactor, BigDecimal JavaDoc amount, BigDecimal JavaDoc quantity,
435             String JavaDoc invoiceItemPostingGlAccountId, String JavaDoc acctgTransTypeId, String JavaDoc offsettingGlAccountTypeId,
436             String JavaDoc organizationPartyId, String JavaDoc transactionPartyId, String JavaDoc transactionPartyRoleTypeId,
437             String JavaDoc defaultDebitCreditFlag, String JavaDoc defaultGlAccountTypeId
438             ) throws GenericServiceException, GenericEntityException {
439
440         int decimals = UtilNumber.getBigDecimalScale("invoice.decimals");
441         int rounding = UtilNumber.getBigDecimalRoundingMode("invoice.rounding");
442
443         // First, determine the amount to credit and update postingTotal
444
BigDecimal JavaDoc postingAmount = conversionFactor.multiply(amount).multiply(quantity).setScale(decimals, rounding);
445         postingTotal = postingTotal.add(postingAmount);
446
447         // Next, determine the amount which has already been debit to uninvoiced receipts, so we can reconcile the difference
448
// between invoice amounts and original order amounts in Uninvoiced Item Receipt
449
BigDecimal JavaDoc orderAmount = new BigDecimal JavaDoc("0.00");
450         List orderItemBillings = invoiceItem.getRelated("OrderItemBilling");
451         for (Iterator iter = orderItemBillings.iterator(); iter.hasNext(); ) {
452             GenericValue orderItemBilling = (GenericValue) iter.next();
453             GenericValue orderItem = orderItemBilling.getRelatedOne("OrderItem");
454             orderAmount = orderAmount.add(conversionFactor
455                     .multiply(orderItem.getBigDecimal("unitPrice"))
456                     .multiply(orderItem.getBigDecimal("quantity"))
457                     .setScale(decimals, rounding));
458         }
459
460         // if there were no associated order items, post full amount to a default account
461
if (orderItemBillings.size() == 0) {
462             invoiceItemPostingGlAccountId = getDefaultGlAccount(invoiceItem, organizationPartyId);
463             orderAmount = postingAmount;
464         }
465
466         // if still no GL account, then it is an error
467
if ((invoiceItemPostingGlAccountId == null) || (invoiceItemPostingGlAccountId.equals(""))) {
468             return ServiceUtil.returnError("Cannot find posting GL account for invoice item " + invoiceItem);
469         }
470
471         // create the transaction entry for uninvoiced receipts or the default account
472
Map acctgTransEntry = new HashMap();
473         acctgTransEntry.put("glAccountId", invoiceItemPostingGlAccountId);
474         acctgTransEntry.put("debitCreditFlag", defaultDebitCreditFlag);
475         acctgTransEntry.put("organizationPartyId", organizationPartyId);
476         acctgTransEntry.put("partyId", transactionPartyId);
477         acctgTransEntry.put("roleTypeId", transactionPartyRoleTypeId);
478         acctgTransEntry.put("acctgTransEntryTypeId", "_NA_");
479         acctgTransEntry.put("productId", invoiceItem.getString("productId"));
480         acctgTransEntry.put("amount", new Double JavaDoc(orderAmount.doubleValue()));
481         acctgTransEntries.add(acctgTransEntry);
482
483         // compute the variance
484
BigDecimal JavaDoc varianceAmount = postingAmount.subtract(orderAmount);
485         if (Debug.verboseOn()) {
486             Debug.logVerbose("Purchase InvoiceItem: orderAmount ["+orderAmount+"], postingAmount ["+postingAmount+"], varianceAmount["+varianceAmount+"]", module);
487         }
488
489         // if there's a variance
490
if (varianceAmount.signum() != 0) {
491
492             // get the inventory price variance gl account
493
String JavaDoc varianceGlAccountId = UtilAccounting.getDefaultAccountId("PURCHASE_PRICE_VAR", organizationPartyId, delegator);
494
495             // and debit the variance account
496
acctgTransEntry = new HashMap(acctgTransEntry);
497             acctgTransEntry.put("glAccountId", varianceGlAccountId);
498             acctgTransEntry.put("amount", new Double JavaDoc(varianceAmount.doubleValue()));
499             acctgTransEntries.add(acctgTransEntry);
500         }
501
502         return UtilMisc.toMap("postingTotal", postingTotal);
503     }
504
505     /**
506      * For sales invoices, if an invoice item is a product, then we will need to post COGS and INVENTORY as well.
507      * TODO: maybe do COGS/INVENTORY posting for other invoiceItemTypeId (easier now with this refactored method)
508      */

509     private static Map processSalesInvoiceItem(
510             GenericDelegator delegator, LocalDispatcher dispatcher, GenericValue userLogin,
511             GenericValue invoice, GenericValue invoiceItem, List acctgTransEntries,
512             BigDecimal JavaDoc postingTotal, BigDecimal JavaDoc conversionFactor, BigDecimal JavaDoc amount, BigDecimal JavaDoc quantity,
513             String JavaDoc invoiceItemPostingGlAccountId, String JavaDoc acctgTransTypeId, String JavaDoc offsettingGlAccountTypeId,
514             String JavaDoc organizationPartyId, String JavaDoc transactionPartyId, String JavaDoc transactionPartyRoleTypeId,
515             String JavaDoc defaultDebitCreditFlag, String JavaDoc defaultGlAccountTypeId
516             ) throws GenericServiceException, GenericEntityException {
517
518         int decimals = UtilNumber.getBigDecimalScale("invoice.decimals");
519         int rounding = UtilNumber.getBigDecimalRoundingMode("invoice.rounding");
520
521         // If there is still no account, try getting a default account
522
if ((invoiceItemPostingGlAccountId == null) || invoiceItemPostingGlAccountId.equals("")) {
523             invoiceItemPostingGlAccountId = getDefaultGlAccount(invoiceItem, organizationPartyId);
524         }
525
526         // if still no GL account, then it is an error
527
if ((invoiceItemPostingGlAccountId == null) || (invoiceItemPostingGlAccountId.equals(""))) {
528             Debug.logError("Canot find GL account to post for this invoice item " + invoiceItem, module);
529             return ServiceUtil.returnError("Cannot find posting GL account for invoice " + invoiceItem.getString("invoiceId")
530                     + ", item " + invoiceItem.getString("invoiceItemSeqId"));
531         }
532
533         // prepare a transaction entry
534
// TODO: if necessary, process theirPartyId, origAmount, dueDate, groupId, description, voucherRef fields of AcctgTransEntry
535
Map acctgTransEntry = new HashMap();
536         acctgTransEntry.put("glAccountId", invoiceItemPostingGlAccountId);
537         acctgTransEntry.put("debitCreditFlag", defaultDebitCreditFlag);
538         acctgTransEntry.put("organizationPartyId", organizationPartyId);
539         acctgTransEntry.put("acctgTransEntryTypeId", "_NA_");
540         acctgTransEntry.put("productId", invoiceItem.getString("productId"));
541
542         // amount to be posted. NOTE: this conversion is rounded AFTER (conversion*amount*quantity)
543
BigDecimal JavaDoc postingAmount = amount.multiply(conversionFactor).multiply(quantity).setScale(decimals, rounding);
544         acctgTransEntry.put("amount", new Double JavaDoc(postingAmount.doubleValue()));
545
546         // if there is a taxAuthPartyId on the invoice item, then this invoice item is a tax transaction.
547
if (invoiceItem.getString("taxAuthPartyId") != null) {
548             // In that case, the partyId on the accounting trans entry should be the the taxAuthPartyId on the invoice item
549
acctgTransEntry.put("partyId", invoiceItem.getString("taxAuthPartyId"));
550             acctgTransEntry.put("roleTypeId", "TAX_AUTHORITY");
551         } else {
552             // TODO: have to determine role here
553
acctgTransEntry.put("partyId", transactionPartyId);
554             acctgTransEntry.put("roleTypeId", transactionPartyRoleTypeId);
555         }
556
557         // update loop variables
558
acctgTransEntries.add(acctgTransEntry);
559         postingTotal = postingTotal.add(postingAmount); // this preserves the postingAmount scale (addition => scale = max(scale1, scale2))
560

561         // next, if an invoice item is a product, then we will need to post COGS and INVENTORY as well.
562

563         // Must check invoiceItemTypeId or you'd end up posting to COGS for adjustment entries, sales tax, etc.
564
if (!(invoiceItem.getString("invoiceItemTypeId").equals("INV_FPROD_ITEM") && (invoiceItem.getString("productId") != null))) {
565             return UtilMisc.toMap("postingTotal", postingTotal);
566         }
567
568         return UtilMisc.toMap("postingTotal", postingTotal);
569     }
570
571     /**
572      * If an invoice item is not a product, or if it has no specific GL account defined,
573      * check if there is a GL account defined generally for this invoice item type and
574      * this accounting organization (party). If there is still no organization-specific
575      * GL account for this invoice item type, then use InvoiceItemType's default GL account.
576      * However, if an overrideGlAccountId is present in the invoiceItem, return that instead.
577      */

578     private static String JavaDoc getDefaultGlAccount(GenericValue invoiceItem, String JavaDoc organizationPartyId) throws GenericEntityException {
579         if (invoiceItem.getString("overrideGlAccountId") != null) return invoiceItem.getString("overrideGlAccountId");
580         GenericValue invoiceItemType = invoiceItem.getRelatedOne("InvoiceItemType");
581         List orgInvoiceItemTypeGlAccounts = invoiceItemType.getRelatedByAnd("InvoiceItemTypeGlAccount", UtilMisc.toMap("organizationPartyId", organizationPartyId));
582         if ((orgInvoiceItemTypeGlAccounts != null) && (orgInvoiceItemTypeGlAccounts.size() == 1)) {
583             return ((GenericValue) orgInvoiceItemTypeGlAccounts.get(0)).getString("glAccountId");
584         } else {
585             return invoiceItemType.getString("defaultGlAccountId");
586         }
587     }
588
589
590     /* ====================================================================== */
591     /* ===== END POST INVOICE TO GL ===== */
592     /* ====================================================================== */
593     
594    
595     /**
596      * Service to post a payment other than tax payments to the General Ledger.
597      * @param paymentId
598      * @return
599      * @author Leon Torres
600      */

601     public static Map postPaymentToGl(DispatchContext dctx, Map context) {
602         GenericDelegator delegator = dctx.getDelegator();
603         LocalDispatcher dispatcher = dctx.getDispatcher();
604         GenericValue userLogin = (GenericValue) context.get("userLogin");
605         
606         String JavaDoc paymentId = (String JavaDoc) context.get("paymentId");
607         try {
608             GenericValue payment = delegator.findByPrimaryKey("Payment", UtilMisc.toMap("paymentId", paymentId));
609             
610             // figure out the parties involved and the payment gl account
611
Map results = dispatcher.runSync("getPaymentAccountAndParties", UtilMisc.toMap("paymentId", paymentId));
612             if (results.get(ModelService.RESPONSE_MESSAGE).equals(ModelService.RESPOND_ERROR)) {
613                 return results;
614             }
615             String JavaDoc organizationPartyId = (String JavaDoc) results.get("organizationPartyId");
616             String JavaDoc transactionPartyId = (String JavaDoc) results.get("transactionPartyId");
617             String JavaDoc paymentGlAccountId = (String JavaDoc) results.get("glAccountId");
618
619             // determine the amount of the payment involved
620
double conversionFactor = UtilFinancial.determineUomConversionFactor(delegator, dispatcher, organizationPartyId, payment.getString("currencyUomId"));
621             if (payment.getDouble("amount") == null) {
622                 return ServiceUtil.returnError("Cannot post Payment to GL: Payment with paymentId " + paymentId + " has no amount.");
623             }
624             double transactionAmount = conversionFactor * payment.getDouble("amount").doubleValue();
625             
626             // These Maps hold glAccountId (String) -> amount (Double) pairs are designed to track how much of the payment goes to each gl account. Right now
627
// they are only used for tax-related payments where a tax authority can have different GL accounts in different Geos, but they
628
// also allow much more flexibility in mapping out payments.
629
Map paymentGlAccountAmounts = UtilMisc.toMap(paymentGlAccountId, new Double JavaDoc(transactionAmount));
630             Map offsettingGlAccountAmounts = new HashMap();
631             
632             if (UtilAccounting.isTaxPayment(payment)) {
633                 // Build a Map of gl account Id and amount based on the Geo specified in PaymentApplication and the combination of partyId
634
// and geo which specify a gl account in TaxAuthorityGlAccount
635
List paymentApplications = payment.getRelated("PaymentApplication");
636                 for (Iterator pAi = paymentApplications.iterator(); pAi.hasNext(); ) {
637                     GenericValue appl = (GenericValue) pAi.next();
638                     if (appl.getString("taxAuthGeoId") != null) {
639                         GenericValue taxAuthGlAccount = delegator.findByPrimaryKeyCache("TaxAuthorityGlAccount",
640                                 UtilMisc.toMap("organizationPartyId", organizationPartyId, "taxAuthPartyId", transactionPartyId, "taxAuthGeoId", appl.getString("taxAuthGeoId")));
641                         offsettingGlAccountAmounts.put(taxAuthGlAccount.getString("glAccountId"), new Double JavaDoc(appl.getDouble("amountApplied").doubleValue() * conversionFactor));
642                     }
643                 }
644             } else {
645                 String JavaDoc offsettingGlAccountId = getOffsettingPaymentGlAccount(dispatcher, payment, organizationPartyId, userLogin);
646                 offsettingGlAccountAmounts.put(offsettingGlAccountId, new Double JavaDoc(transactionAmount));
647             }
648             
649             // determine which to credit and debit
650
Map creditGlAccountAmounts = null;
651             Map debitGlAccountAmounts = null;
652             if (UtilAccounting.isDisbursement(payment)) {
653                 creditGlAccountAmounts = paymentGlAccountAmounts;
654                 debitGlAccountAmounts = offsettingGlAccountAmounts;
655             } else if (UtilAccounting.isReceipt(payment)) {
656                 creditGlAccountAmounts = offsettingGlAccountAmounts;
657                 debitGlAccountAmounts = paymentGlAccountAmounts;
658             } else {
659                 return ServiceUtil.returnError("Cannot Post Payment to GL: Payment with paymentId " + paymentId
660                         + " has unsupported paymentTypeId " + payment.getString("paymentTypeId") +
661                                 " (Must be or have a parent type of DISBURSEMENT or RECEIPT.)");
662             }
663             
664             if ((creditGlAccountAmounts == null) || (creditGlAccountAmounts.keySet().size() == 0)) {
665                 return ServiceUtil.returnError("No credit GL accounts found for payment posting");
666             }
667             if (debitGlAccountAmounts == null) {
668                 return ServiceUtil.returnError("No debit GL accounts found for payment posting");
669             }
670             
671             List acctgTransEntries = makePaymentEntries(creditGlAccountAmounts, debitGlAccountAmounts,
672                     organizationPartyId, transactionPartyId, delegator);
673             
674             // Post transaction
675
Map tmpMap = UtilMisc.toMap("acctgTransEntries", acctgTransEntries,
676                     "glFiscalTypeId", "ACTUAL", "acctgTransTypeId", "PAYMENT_ACCTG_TRANS",
677                     "transactionDate", UtilDateTime.nowTimestamp(), "userLogin", userLogin);
678             tmpMap.put("paymentId", paymentId);
679             tmpMap.put("partyId", transactionPartyId);
680             tmpMap = dispatcher.runSync("createAcctgTransAndEntries", tmpMap);
681
682             if (((String JavaDoc) tmpMap.get(ModelService.RESPONSE_MESSAGE)).equals(ModelService.RESPOND_SUCCESS)) {
683                 results = ServiceUtil.returnSuccess();
684                 results.put("acctgTransId", tmpMap.get("acctgTransId"));
685                 return(results);
686             } else {
687                 return tmpMap;
688             }
689         } catch (GenericEntityException ex) {
690             return(ServiceUtil.returnError(ex.getMessage()));
691         } catch (GenericServiceException ex) {
692             return(ServiceUtil.returnError(ex.getMessage()));
693         }
694     }
695
696     /**
697      * Get the offsetting account for a payment.
698      * @param dispatcher
699      * @param payment
700      * @param organizationPartyId
701      * @param userLogin
702      * @return The offsetting glAccountId of a payment or null if one cannot be found
703      * @throws GenericServiceException if no GlAccount was configured for the organization
704      */

705     public static String JavaDoc getOffsettingPaymentGlAccount(LocalDispatcher dispatcher, GenericValue payment, String JavaDoc organizationPartyId, GenericValue userLogin)
706         throws GenericServiceException, GenericEntityException {
707         // Find the type of the offsetting GL Account (ACCOUNTS_RECEIVABLE, INVENTORY, etc.) in GlAccountTypeDefault based on glAccoutTypeId of the PaymentType
708
List tmpList = payment.getRelatedOne("PaymentType").getRelatedByAnd("PaymentGlAccountTypeMap", UtilMisc.toMap("organizationPartyId", organizationPartyId));
709         if (tmpList.size() == 0) {
710             throw new AccountingException("Offsetting GL account for payment type " + payment.getString("paymentTypeId")
711                     + " of organization " + organizationPartyId + " has not been configured.");
712         }
713         String JavaDoc offsettingGlAccountTypeId = ((GenericValue) tmpList.get(0)).getString("glAccountTypeId");
714
715         // get the GL account from the type
716
return UtilAccounting.getDefaultAccountId(offsettingGlAccountTypeId, organizationPartyId, userLogin.getDelegator());
717     }
718
719     /**
720      * A little helper method to postPaymentToGl. Maybe one day it'll get a promotion and help the other services too?
721      * @param creditGlAccountAmounts
722      * @param debitGlAccountAmounts
723      * @param organizationPartyId
724      * @param transactionPartyId
725      * @throws GenericEntityException
726      */

727     private static List makePaymentEntries(Map creditGlAccountAmounts, Map debitGlAccountAmounts,
728             String JavaDoc organizationPartyId, String JavaDoc transactionPartyId, GenericDelegator delegator) throws GenericEntityException {
729         List acctgTransEntries = new LinkedList();
730         int itemSeq = 1;
731         for (Iterator ai = creditGlAccountAmounts.keySet().iterator(); ai.hasNext(); ) {
732             String JavaDoc creditGlAccountId = (String JavaDoc) ai.next();
733             Map tmpMap = UtilMisc.toMap("glAccountId", creditGlAccountId, "debitCreditFlag", "C",
734                     "amount", creditGlAccountAmounts.get(creditGlAccountId), "acctgTransEntrySeqId", Integer.toString(itemSeq),
735                     "organizationPartyId", organizationPartyId, "acctgTransEntryTypeId", "_NA_");
736             tmpMap.put("partyId", transactionPartyId);
737             GenericValue creditAcctTransEntry = delegator.makeValue("AcctgTransEntry", tmpMap);
738             acctgTransEntries.add(creditAcctTransEntry);
739             itemSeq++;
740         }
741         
742         for (Iterator ai = debitGlAccountAmounts.keySet().iterator(); ai.hasNext(); ) {
743             String JavaDoc debitGlAccountId = (String JavaDoc) ai.next();
744             Map tmpMap = UtilMisc.toMap("glAccountId", debitGlAccountId, "debitCreditFlag", "D",
745                     "amount", debitGlAccountAmounts.get(debitGlAccountId), "acctgTransEntrySeqId", Integer.toString(itemSeq),
746                     "organizationPartyId", organizationPartyId, "acctgTransEntryTypeId", "_NA_");
747             tmpMap.put("partyId", transactionPartyId);
748             GenericValue debitAcctTransEntry = delegator.makeValue("AcctgTransEntry", tmpMap);
749             acctgTransEntries.add(debitAcctTransEntry);
750             itemSeq++;
751         }
752         
753         return acctgTransEntries;
754     }
755
756     /**
757      * Service to determine accounting parties and GL account of a payment
758      * @param paymentId
759      * @return
760      * @author Leon Torres
761      */

762     public static Map getPaymentAccountAndParties(DispatchContext dctx, Map context) {
763         GenericDelegator delegator = dctx.getDelegator();
764         LocalDispatcher dispatcher = dctx.getDispatcher();
765         GenericValue userLogin = (GenericValue) context.get("userLogin");
766         
767         String JavaDoc paymentId = (String JavaDoc) context.get("paymentId");
768         // return values
769
Map result = ServiceUtil.returnSuccess();
770         String JavaDoc organizationPartyId = null;
771         String JavaDoc transactionPartyId = null;
772         String JavaDoc glAccountId = null;
773         try {
774             GenericValue payment = delegator.findByPrimaryKey("Payment", UtilMisc.toMap("paymentId", paymentId));
775             if (payment == null) {
776                 return ServiceUtil.returnError("Payment " + paymentId + " doesn't exist!");
777             }
778                                                                                                                                     
779             // step 0 figure out which party is debit vs. credit
780
if (UtilAccounting.isDisbursement(payment)) {
781                 organizationPartyId = payment.getString("partyIdFrom");
782                 transactionPartyId = payment.getString("partyIdTo");
783             } else if (UtilAccounting.isReceipt(payment)) {
784                 organizationPartyId = payment.getString("partyIdTo");
785                 transactionPartyId = payment.getString("partyIdFrom");
786             } else {
787                 return ServiceUtil.returnError("Payment with paymentId " + paymentId + " has a type which is not DISBURSEMENT or RECEIPT.");
788             }
789             result.put("organizationPartyId", organizationPartyId);
790             result.put("transactionPartyId", transactionPartyId);
791
792             // step one, look for glAccountId in PaymentMethod
793
GenericValue paymentMethod = payment.getRelatedOne("PaymentMethod");
794             if (paymentMethod != null) {
795                 glAccountId = paymentMethod.getString("glAccountId");
796                 if (glAccountId != null) {
797                     result.put("glAccountId", glAccountId);
798                     return result;
799                 }
800             }
801
802             // step two: glAccountId from CreditCardTypeGlAccount
803
if ("CREDIT_CARD".equals(payment.getString("paymentMethodTypeId"))) {
804                 GenericValue cc = payment.getRelatedOne("CreditCard");
805                 if (cc == null) {
806                     Debug.logWarning("Cannot find Gl Account from CreditCartTypeGlAccount: Credit Card not found for Payment with paymentId " + payment.getString("paymentId") + ". Trying Gl Account for Credit Cards or default Gl Account instead.", module);
807                 } else {
808                     GenericValue ccGlAccount = delegator.findByPrimaryKey("CreditCardTypeGlAccount", UtilMisc.toMap("organizationPartyId", organizationPartyId, "cardType", cc.getString("cardType")));
809                     if (ccGlAccount != null) {
810                         result.put("glAccountId", ccGlAccount.getString("glAccountId"));
811                         return result;
812                     }
813                 }
814             }
815
816             // step three: see if the payment method type has a gl account via PaymentMethodTypeGlAccount
817
List tmpList = payment.getRelatedOne("PaymentMethodType").getRelatedByAnd("PaymentMethodTypeGlAccount",
818                     UtilMisc.toMap("organizationPartyId", organizationPartyId));
819             if (tmpList.size() > 0) {
820                 GenericValue paymentMethodTypeGlAccount = (GenericValue) tmpList.get(0);
821                 glAccountId = paymentMethodTypeGlAccount.getString("glAccountId");
822                 if (glAccountId != null) {
823                     result.put("glAccountId", glAccountId);
824                     return result;
825                 }
826             }
827
828             // step four: defaultGlAccountId
829
GenericValue paymentMethodType = payment.getRelatedOne("PaymentMethodType");
830             if (paymentMethodType != null) {
831                 glAccountId = paymentMethodType.getString("defaultGlAccountId");
832                 if (glAccountId != null) {
833                     result.put("glAccountId", glAccountId);
834                     return result;
835                 }
836             }
837             
838             return ServiceUtil.returnError("No GL Account found for Payment with paymentId " + paymentId);
839         } catch (GenericEntityException ex) {
840             return(ServiceUtil.returnError(ex.getMessage()));
841         }
842     }
843
844     /**
845      * Service to match a Payment to an Invoice for a particular PaymentApplication.
846      */

847     public static Map matchPaymentInvoiceGlPosts(DispatchContext dctx, Map context) {
848         GenericDelegator delegator = dctx.getDelegator();
849         LocalDispatcher dispatcher = dctx.getDispatcher();
850         GenericValue userLogin = (GenericValue) context.get("userLogin");
851
852         String JavaDoc paymentApplicationId = (String JavaDoc) context.get("paymentApplicationId");
853         try {
854             GenericValue paymentApplication = delegator.findByPrimaryKey("PaymentApplication", UtilMisc.toMap("paymentApplicationId", paymentApplicationId));
855             GenericValue payment = paymentApplication.getRelatedOne("Payment");
856             GenericValue invoice = paymentApplication.getRelatedOne("Invoice");
857             String JavaDoc paymentId = payment.getString("paymentId");
858             String JavaDoc invoiceId = invoice.getString("invoiceId");
859
860             if (invoice == null) {
861                 throw new GenericServiceException("Could not find Invoice with ID [" + invoiceId + "]");
862             }
863
864             // transaction information
865
String JavaDoc organizationPartyId = null;
866             String JavaDoc paymentOffsetDebitCreditFlag = null;
867             String JavaDoc invoiceOffsetDebitCreditFlag = null;
868             String JavaDoc invoiceOffsettingGlAccountTypeId = null;
869             String JavaDoc transactionPartyId = payment.getString("partyIdTo");
870             String JavaDoc transactionPartyRoleTypeId = payment.getString("roleTypeIdTo");
871             String JavaDoc acctgTransTypeId = "OTHER_INTERNAL_ACCTG";
872
873             // determine the organization, offsetting account type, and debitCreditFlags, but first make sure that we're only
874
// doing this for SALES invoices and payment receipts or PURCHASE invoices and disbursements
875
if ((UtilAccounting.isDisbursement(payment)) && (invoice.getString("invoiceTypeId").equals("PURCHASE_INVOICE"))) {
876                 organizationPartyId = payment.getString("partyIdFrom");
877                 paymentOffsetDebitCreditFlag = "D";
878                 invoiceOffsetDebitCreditFlag = "C";
879                 invoiceOffsettingGlAccountTypeId = "ACCOUNTS_PAYABLE";
880             } else if ((UtilAccounting.isReceipt(payment)) && (invoice.getString("invoiceTypeId").equals("SALES_INVOICE"))) { // Receipt
881
organizationPartyId = payment.getString("partyIdTo");
882                 paymentOffsetDebitCreditFlag = "C";
883                 invoiceOffsetDebitCreditFlag = "D";
884                 invoiceOffsettingGlAccountTypeId = "ACCOUNTS_RECEIVABLE";
885             } else {
886                 // possibly other types of payments or invoices involved, such as Customer Return Invoices and Customer Refunds.
887
return ServiceUtil.returnSuccess();
888             }
889
890             // get the offsetting account of the payment
891
String JavaDoc paymentOffsettingGlAccountId = getOffsettingPaymentGlAccount(dispatcher, payment, organizationPartyId, userLogin);
892
893             // get the offsetting gl account of the invoice
894
String JavaDoc invoiceOffsettingGlAccountId = UtilAccounting.getDefaultAccountId(invoiceOffsettingGlAccountTypeId, organizationPartyId, delegator);
895
896             // if the accounts are the same, there's no need to match
897
if (paymentOffsettingGlAccountId == invoiceOffsettingGlAccountId) {
898                 if (Debug.verboseOn()) {
899                     Debug.logVerbose("Matching payment to invoice: Payment and Invoice offsetting accounts were identical. No need to match.", module);
900                 }
901                 return ServiceUtil.returnSuccess();
902             }
903
904             // make the transaction entry for the offsetting payment account
905
Map input = new HashMap();
906             input.put("glAccountId", paymentOffsettingGlAccountId);
907             input.put("acctgTransEntrySeqId", "1");
908             input.put("organizationPartyId", organizationPartyId);
909             input.put("partyId", transactionPartyId);
910             input.put("roleTypeId", transactionPartyRoleTypeId);
911             input.put("debitCreditFlag", invoiceOffsetDebitCreditFlag); // want opposite flag
912
input.put("amount", paymentApplication.getDouble("amountApplied"));
913             input.put("acctgTransEntryTypeId", "_NA_");
914             input.put("description", "Matching GL accounts for invoice " + invoiceId + " and payment " + paymentId);
915             GenericValue paymentEntry = delegator.makeValue("AcctgTransEntry", input);
916
917             // make the transaction entry for the offsetting invoice account
918
input = new HashMap();
919             input.put("glAccountId", invoiceOffsettingGlAccountId);
920             input.put("acctgTransEntrySeqId", "2");
921             input.put("organizationPartyId", organizationPartyId);
922             input.put("partyId", transactionPartyId);
923             input.put("roleTypeId", transactionPartyRoleTypeId);
924             input.put("debitCreditFlag", paymentOffsetDebitCreditFlag); // want opposite flag
925
input.put("amount", paymentApplication.getDouble("amountApplied"));
926             input.put("acctgTransEntryTypeId", "_NA_");
927             input.put("description", "Matching GL accounts for invoice " + invoiceId + " and payment " + paymentId);
928             GenericValue invoiceEntry = delegator.makeValue("AcctgTransEntry", input);
929
930             // prepare the transaction
931
input = new HashMap();
932             input.put("acctgTransEntries", UtilMisc.toList(paymentEntry, invoiceEntry));
933             input.put("invoiceId", invoiceId);
934             input.put("partyId", transactionPartyId);
935             input.put("roleTypeId", transactionPartyRoleTypeId);
936             input.put("glFiscalTypeId", "ACTUAL");
937             input.put("acctgTransTypeId", acctgTransTypeId);
938             input.put("userLogin", userLogin);
939
940             // create the transaction and return the acctgTransId
941
return dispatcher.runSync("createAcctgTransAndEntries", input);
942
943         } catch (GenericEntityException ee) {
944             return ServiceUtil.returnError(ee.getMessage());
945         } catch (GenericServiceException se) {
946             return ServiceUtil.returnError(se.getMessage());
947         }
948     }
949
950     /**
951      * Service to post an inventory variance transaction to the General Ledger
952      * @param inventoryItemId
953      * @param physicalInventoryId
954      * @return
955      * @author Leon Torres
956      */

957     public static Map postInventoryVarianceToGl(DispatchContext dctx, Map context) {
958         GenericDelegator delegator = dctx.getDelegator();
959         LocalDispatcher dispatcher = dctx.getDispatcher();
960         GenericValue userLogin = (GenericValue) context.get("userLogin");
961
962         String JavaDoc inventoryItemId = (String JavaDoc) context.get("inventoryItemId");
963         String JavaDoc physicalInventoryId = (String JavaDoc) context.get("physicalInventoryId");
964         try {
965             Map tmpMap = new HashMap();
966
967             GenericValue inventoryVariance = delegator.findByPrimaryKey("InventoryItemVariance",
968                     UtilMisc.toMap("inventoryItemId", inventoryItemId, "physicalInventoryId", physicalInventoryId));
969             if (inventoryVariance == null) {
970                 return ServiceUtil.returnError("No InventoryVariance entity record for inventoryItemId " + inventoryItemId + " and physicalInventoryId " + physicalInventoryId);
971             }
972             
973             BigDecimal JavaDoc quantityOnHandVar = inventoryVariance.getBigDecimal("quantityOnHandVar");
974             if (quantityOnHandVar == null || quantityOnHandVar.compareTo(ZERO) == 0) {
975                 // no actual inventory loss or gain to account for
976
return ServiceUtil.returnSuccess();
977             }
978             
979             GenericValue inventoryItem = inventoryVariance.getRelatedOne("InventoryItem");
980             String JavaDoc productId = inventoryItem.getString("productId");
981             // owner of inventory item
982
String JavaDoc ownerPartyId = inventoryItem.getString("ownerPartyId");
983             // get the inventory item unit cost
984
BigDecimal JavaDoc unitCost = inventoryItem.getBigDecimal("unitCost");
985             // get the currency conversion factor
986
BigDecimal JavaDoc conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, ownerPartyId, inventoryItem.getString("currencyUomId")));
987             // validate the unitCost and compute transaction amount
988
if (unitCost == null) {
989                 return ServiceUtil.returnError("Could not determine unitCost of product [" + productId +
990                                                "] for inventory variance [" + physicalInventoryId +
991                                                "] and inventory item [" + inventoryItemId + "]");
992             }
993             // convert the intentory item's unit cost into the owner's currency
994
unitCost = unitCost.multiply(conversionFactor).setScale(decimals, rounding);
995             // The transaction amount is: amount = quantityOnHandVar * unitCost
996
BigDecimal JavaDoc transactionAmount = unitCost.multiply(quantityOnHandVar).setScale(decimals, rounding);
997             
998             // get owner's party COGS method
999
GenericValue acctgPref = delegator.findByPrimaryKeyCache("PartyAcctgPreference", UtilMisc.toMap("partyId", ownerPartyId));
1000            String JavaDoc cogsMethodId = acctgPref.getString("cogsMethodId");
1001            
1002            // If method is COGS_AVG_COST, also compute the inventory adjustment amount = (prodAvgCost - unitCost) * quantityOnHandVar
1003
BigDecimal JavaDoc inventoryAdjAmount = null;
1004            if ((cogsMethodId != null) && (cogsMethodId.equals("COGS_AVG_COST"))) {
1005                BigDecimal JavaDoc prodAvgCost = UtilCOGS.getProductAverageCost(productId, ownerPartyId, userLogin, delegator, dispatcher);
1006                if (prodAvgCost == null) return ServiceUtil.returnError("Unable to find a product average cost for product ["+productId+"] in organization ["+ownerPartyId+"]");
1007                // TODO: there could be rounding issues here; maybe it's better to do something like this:
1008
// (prodAvgCost * quantityOnHandVar) - (unitCost * quantityOnHandVar) and then set the scale.
1009
inventoryAdjAmount = prodAvgCost.subtract(unitCost).multiply(quantityOnHandVar).setScale(decimals, rounding);
1010            }
1011
1012            // Inventory GL account
1013
String JavaDoc invGlAcctId = UtilAccounting.getProductOrgGlAccountId(productId, "INVENTORY_ACCOUNT", ownerPartyId, delegator);
1014
1015            // Variance Expense GL Account
1016
// TODO: At some point, maybe we should add an accountTypeId field to the primary key of the VarianceReasonGlAccount so that
1017
// we can set, for each and every variance reason, the account for the primary transaction and
1018
// the account for the adjustment?
1019
// For now I'll assume that the credit (C) accounts for the primary transaction
1020
// and for the adjustment transaction are the same.
1021
GenericValue varianceReason = inventoryVariance.getRelatedOne("VarianceReason");
1022            GenericValue varExpGlAcct = EntityUtil.getFirst(varianceReason.getRelatedByAnd("VarianceReasonGlAccount", UtilMisc.toMap("organizationPartyId", ownerPartyId)));
1023            if (varExpGlAcct == null) {
1024                return ServiceUtil.returnError("Could not find Variance Expense GL Account for variance reason ["+varianceReason.get("description")+"].");
1025            }
1026            String JavaDoc varExpGlAcctId = (String JavaDoc) varExpGlAcct.get("glAccountId");
1027            
1028            // ===========================================================
1029
// Transaction to credit the variance expense GL acct
1030
tmpMap = UtilMisc.toMap("glAccountId", varExpGlAcctId, "debitCreditFlag", "C",
1031                "amount", new Double JavaDoc(transactionAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(0),
1032                "organizationPartyId", ownerPartyId, "acctgTransEntryTypeId", "_NA_");
1033            tmpMap.put("productId", productId);
1034            GenericValue varExpGlAcctTrans = delegator.makeValue("AcctgTransEntry", tmpMap);
1035
1036            // Transaction to debit the inventory GL acct
1037
tmpMap = UtilMisc.toMap("glAccountId", invGlAcctId, "debitCreditFlag", "D",
1038                "amount", new Double JavaDoc(transactionAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(1),
1039                "organizationPartyId", ownerPartyId, "acctgTransEntryTypeId", "_NA_");
1040            tmpMap.put("productId", productId);
1041            GenericValue invGlAcctTrans = delegator.makeValue("AcctgTransEntry", tmpMap);
1042
1043            List transEntries = UtilMisc.toList(varExpGlAcctTrans, invGlAcctTrans);
1044            
1045            // Adjustment transaction for the difference of unit cost and average cost
1046
if ((inventoryAdjAmount != null) && (inventoryAdjAmount.compareTo(ZERO) != 0)) {
1047                // Inventory GL account for inventory cost adjustments
1048
String JavaDoc invGlAcctAdjId = UtilAccounting.getProductOrgGlAccountId(productId, "INV_ADJ_AVG_COST", ownerPartyId, delegator);
1049                // TODO: for now I'll assume that the credit (C) account
1050
// for the adjustment transaction is the same one of the
1051
// primary transaction.
1052
// Transaction to credit the variance expense GL acct
1053
tmpMap = UtilMisc.toMap("glAccountId", varExpGlAcctId, "debitCreditFlag", "C",
1054                    "amount", new Double JavaDoc(inventoryAdjAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(0),
1055                    "organizationPartyId", ownerPartyId, "acctgTransEntryTypeId", "_NA_");
1056                tmpMap.put("productId", productId);
1057                GenericValue varExpGlAcctAdjTrans = delegator.makeValue("AcctgTransEntry", tmpMap);
1058                transEntries.add(varExpGlAcctAdjTrans);
1059
1060                // Transaction to debit the inventory GL acct
1061
tmpMap = UtilMisc.toMap("glAccountId", invGlAcctAdjId, "debitCreditFlag", "D",
1062                    "amount", new Double JavaDoc(inventoryAdjAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(1),
1063                    "organizationPartyId", ownerPartyId, "acctgTransEntryTypeId", "_NA_");
1064                tmpMap.put("productId", productId);
1065                GenericValue invGlAcctAdjTrans = delegator.makeValue("AcctgTransEntry", tmpMap);
1066                transEntries.add(invGlAcctAdjTrans);
1067            }
1068            // Perform the transaction
1069
tmpMap = UtilMisc.toMap("acctgTransEntries", transEntries,
1070                    "glFiscalTypeId", "ACTUAL", "acctgTransTypeId", "ITEM_VARIANCE_ACCTG_",
1071                    "transactionDate", UtilDateTime.nowTimestamp(),
1072                    "userLogin", userLogin);
1073            tmpMap.put("inventoryItemId", inventoryItemId);
1074            tmpMap.put("physicalInventoryId", physicalInventoryId);
1075            tmpMap = dispatcher.runSync("createAcctgTransAndEntries", tmpMap);
1076
1077            if (ServiceUtil.isError(tmpMap)) {
1078                return tmpMap;
1079            }
1080            Map result = ServiceUtil.returnSuccess();
1081            result.put("acctgTransId", tmpMap.get("acctgTransId"));
1082            return result;
1083        } catch (GenericEntityException ex) {
1084            return(ServiceUtil.returnError(ex.getMessage()));
1085        } catch (GenericServiceException ex) {
1086            return(ServiceUtil.returnError(ex.getMessage()));
1087        }
1088    }
1089
1090    /**
1091     * Service to post outbound Shipments to GL.
1092     * @author Jacopo Cappellato
1093     */

1094    public static Map postShipmentToGl(DispatchContext dctx, Map context) {
1095        GenericDelegator delegator = dctx.getDelegator();
1096        LocalDispatcher dispatcher = dctx.getDispatcher();
1097        GenericValue userLogin = (GenericValue) context.get("userLogin");
1098
1099        String JavaDoc shipmentId = (String JavaDoc) context.get("shipmentId");
1100        try {
1101            List issuances = delegator.findByAnd("ItemIssuance", UtilMisc.toMap("shipmentId", shipmentId));
1102            Iterator issuancesIt = issuances.iterator();
1103            List transEntries = new ArrayList();
1104            Map input = null;
1105            String JavaDoc partyIdTo = null;
1106            while (issuancesIt.hasNext()) {
1107                GenericValue itemIssuance = (GenericValue)issuancesIt.next();
1108                GenericValue inventoryItem = itemIssuance.getRelatedOne("InventoryItem");
1109                
1110                GenericValue orderHeader = itemIssuance.getRelatedOne("OrderHeader");
1111                GenericValue orderRole = EntityUtil.getFirst(orderHeader.getRelatedByAnd("OrderRole", UtilMisc.toMap("roleTypeId", "BILL_TO_CUSTOMER")));
1112                partyIdTo = orderRole.getString("partyId");
1113                
1114                String JavaDoc productId = inventoryItem.getString("productId");
1115                BigDecimal JavaDoc quantityIssued = itemIssuance.getBigDecimal("quantity");
1116                // get the inventory item's owner
1117
String JavaDoc ownerPartyId = inventoryItem.getString("ownerPartyId");
1118                // get the inventory item unit cost
1119
BigDecimal JavaDoc unitCost = inventoryItem.getBigDecimal("unitCost");
1120                // get the currency conversion factor
1121
BigDecimal JavaDoc conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, ownerPartyId, inventoryItem.getString("currencyUomId")));
1122                // validate the unitCost and compute transaction amount
1123
if (unitCost == null) {
1124                    return ServiceUtil.returnError("Could not determine unitCost of product [" + productId +
1125                                                   "] for item issuance [" + itemIssuance.getString("itemIssuanceId") + "] and inventory item [" + inventoryItem + "]");
1126                }
1127                // convert the intentory item's unit cost into the owner's currency
1128
unitCost = unitCost.multiply(conversionFactor).setScale(decimals, rounding);
1129                BigDecimal JavaDoc transactionAmount = unitCost.multiply(quantityIssued).setScale(decimals, rounding);
1130                // Inventory GL account
1131
String JavaDoc invGlAcctId = UtilAccounting.getProductOrgGlAccountId(productId, "INVENTORY_ACCOUNT", ownerPartyId, delegator);
1132                String JavaDoc invGlAcctCogsId = UtilAccounting.getProductOrgGlAccountId(productId, "COGS_ACCOUNT", ownerPartyId, delegator);
1133                
1134                // Transaction to credit the inventory account
1135
input = UtilMisc.toMap("glAccountId", invGlAcctId, "organizationPartyId", ownerPartyId, "partyId", partyIdTo);
1136                input.put("productId", productId);
1137                input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1138                input.put("acctgTransEntryTypeId", "_NA_");
1139                input.put("debitCreditFlag", "C");
1140                input.put("acctgTransEntrySeqId", Integer.toString(0));
1141                input.put("roleTypeId", "BILL_TO_CUSTOMER");
1142                GenericValue creditAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1143                transEntries.add(creditAcctTrans);
1144                
1145                // Transaction to debit the cogs account
1146
input = UtilMisc.toMap("glAccountId", invGlAcctCogsId, "organizationPartyId", ownerPartyId, "partyId", partyIdTo);
1147                input.put("productId", productId);
1148                input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1149                input.put("acctgTransEntryTypeId", "_NA_");
1150                input.put("debitCreditFlag", "D");
1151                input.put("acctgTransEntrySeqId", Integer.toString(1));
1152                input.put("roleTypeId", "BILL_TO_CUSTOMER");
1153                GenericValue debitAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1154                transEntries.add(debitAcctTrans);
1155                
1156                // Get owner's party COGS method. If method is COGS_AVG_COST, also compute the inventory adjustment amount = (prodAvgCost - unitCost) * quantityOnHandVar
1157
GenericValue acctgPref = delegator.findByPrimaryKeyCache("PartyAcctgPreference", UtilMisc.toMap("partyId", ownerPartyId));
1158                String JavaDoc cogsMethodId = acctgPref.getString("cogsMethodId");
1159                BigDecimal JavaDoc inventoryAdjAmount = null;
1160                if ((cogsMethodId != null) && (cogsMethodId.equals("COGS_AVG_COST"))) {
1161                    BigDecimal JavaDoc prodAvgCost = UtilCOGS.getProductAverageCost(productId, ownerPartyId, userLogin, delegator, dispatcher);
1162                    if (prodAvgCost == null) {
1163                         Debug.logWarning("Unable to find a product average cost for product ["+productId+"] in organization ["+ownerPartyId+"], assuming zero", module);
1164                         prodAvgCost = ZERO;
1165                    }
1166                    // TODO: there could be rounding issues here; maybe it's better to do something like this:
1167
// (prodAvgCost * quantityOnHandVar) - (unitCost * quantityOnHandVar) and then set the scale.
1168
inventoryAdjAmount = prodAvgCost.subtract(unitCost).multiply(quantityIssued).setScale(decimals, rounding);
1169                }
1170                // Adjustment accounting transaction
1171
if ((inventoryAdjAmount != null) && (inventoryAdjAmount.compareTo(ZERO) != 0)) {
1172                    // GL accounts for cost adjustments due to Average Cost. Right now we're going to use a separate INVENTORY AVG_COST adjustment, but the COGS adjustment just goes to COGS
1173
String JavaDoc invGlAcctAdjId = UtilAccounting.getProductOrgGlAccountId(productId, "INV_ADJ_AVG_COST", ownerPartyId, delegator);
1174                    String JavaDoc invGlAcctAdjCogsId = UtilAccounting.getProductOrgGlAccountId(productId, "COGS_ADJ_AVG_COST", ownerPartyId, delegator);
1175                    // Transaction to credit the adj inventory GL acct
1176
input = UtilMisc.toMap("glAccountId", invGlAcctAdjId, "debitCreditFlag", "C",
1177                        "amount", new Double JavaDoc(inventoryAdjAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(0),
1178                        "organizationPartyId", ownerPartyId, "acctgTransEntryTypeId", "_NA_");
1179                    input.put("productId", productId);
1180                    input.put("partyId", partyIdTo);
1181                    input.put("roleTypeId", "BILL_TO_CUSTOMER");
1182                    transEntries.add(delegator.makeValue("AcctgTransEntry", input));
1183
1184                    // Transaction to debit the adj cogs GL acct
1185
input = UtilMisc.toMap("glAccountId", invGlAcctAdjCogsId, "debitCreditFlag", "D",
1186                        "amount", new Double JavaDoc(inventoryAdjAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(1),
1187                        "organizationPartyId", ownerPartyId, "acctgTransEntryTypeId", "_NA_");
1188                    input.put("productId", productId);
1189                    input.put("partyId", partyIdTo);
1190                    input.put("roleTypeId", "BILL_TO_CUSTOMER");
1191                    transEntries.add(delegator.makeValue("AcctgTransEntry", input));
1192                }
1193            }
1194            // Perform the transaction
1195
input = UtilMisc.toMap("transactionDate", UtilDateTime.nowTimestamp(), "userLogin", userLogin);
1196            input.put("acctgTransEntries", transEntries);
1197            input.put("glFiscalTypeId", "ACTUAL");
1198            input.put("acctgTransTypeId", "OBLIGATION_ACCTG_TRA");
1199            input.put("shipmentId", shipmentId);
1200
1201            Map servResult = dispatcher.runSync("createAcctgTransAndEntries", input);
1202
1203            if (ServiceUtil.isError(servResult)) {
1204                return servResult;
1205            }
1206
1207            Map result = ServiceUtil.returnSuccess();
1208            result.put("acctgTransId", servResult.get("acctgTransId"));
1209
1210        } catch (GenericEntityException ex) {
1211            return(ServiceUtil.returnError(ex.getMessage()));
1212        } catch (GenericServiceException ex) {
1213            return(ServiceUtil.returnError(ex.getMessage()));
1214        }
1215        
1216        return ServiceUtil.returnSuccess();
1217    }
1218   
1219    /**
1220     * Service to post a shipment receipt to GL.
1221     * @author Leon Torres
1222     */

1223    public static Map postShipmentReceiptToGl(DispatchContext dctx, Map context) {
1224        GenericDelegator delegator = dctx.getDelegator();
1225        LocalDispatcher dispatcher = dctx.getDispatcher();
1226        GenericValue userLogin = (GenericValue) context.get("userLogin");
1227
1228        String JavaDoc receiptId = (String JavaDoc) context.get("receiptId");
1229        try {
1230            GenericValue receipt = delegator.findByPrimaryKey("ShipmentReceipt", UtilMisc.toMap("receiptId", receiptId));
1231            String JavaDoc shipmentId = receipt.getString("shipmentId");
1232            String JavaDoc orderId = receipt.getString("orderId");
1233            String JavaDoc orderItemSeqId = receipt.getString("orderItemSeqId");
1234            String JavaDoc productId = receipt.getString("productId");
1235            BigDecimal JavaDoc quantityReceived = receipt.getBigDecimal("quantityAccepted");
1236            GenericValue inventoryItem = receipt.getRelatedOne("InventoryItem");
1237            // get the inventory item's owner
1238
String JavaDoc organizationPartyId = inventoryItem.getString("ownerPartyId");
1239            // get the inventory item unit cost
1240
BigDecimal JavaDoc unitCost = inventoryItem.getBigDecimal("unitCost");
1241            // get the currency conversion factor
1242
BigDecimal JavaDoc conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, organizationPartyId, inventoryItem.getString("currencyUomId")));
1243            
1244            // Use the shipment to get the origin party and determine if this is actually a return, in which case the offsetting account is COGS
1245
String JavaDoc originPartyId = null;
1246            String JavaDoc offsettingGlAccountTypeId = "UNINVOICED_SHIP_RCPT";
1247            if (shipmentId != null) {
1248                GenericValue shipment = receipt.getRelatedOne("Shipment");
1249                originPartyId = shipment.getString("partyIdFrom");
1250                if ((shipment != null) && (shipment.getString("shipmentTypeId").equals("SALES_RETURN"))) {
1251                    offsettingGlAccountTypeId = "COGS_ACCOUNT";
1252                }
1253            }
1254            // validate the unitCost and compute transaction amount
1255
if (unitCost == null) {
1256                return ServiceUtil.returnError("Could not determine unitCost of product [" + productId + "] from shipment receipt [" + receiptId + "]");
1257            }
1258            BigDecimal JavaDoc transactionAmount = unitCost.multiply(quantityReceived).multiply(conversionFactor).setScale(decimals, rounding);
1259
1260            // get the inventory GL account
1261
String JavaDoc inventoryGlAccount = UtilAccounting.getProductOrgGlAccountId(productId, "INVENTORY_ACCOUNT", organizationPartyId, delegator);
1262            // get the offsetting gl account, either uninvoiced receipts for purchase shipments or cogs for returns shipments
1263
String JavaDoc uninvoicedGlAccountId = null;
1264            if (offsettingGlAccountTypeId.equals("COGS_ACCOUNT")) {
1265                // this kind of clunkiness results from ProductGlAccountType and GlAccountType being different fields and values
1266
uninvoicedGlAccountId = UtilAccounting.getProductOrgGlAccountId(productId, offsettingGlAccountTypeId, organizationPartyId, delegator);
1267            } else {
1268                uninvoicedGlAccountId = UtilAccounting.getDefaultAccountId(offsettingGlAccountTypeId, organizationPartyId, delegator);
1269            }
1270            
1271            // Transaction to credit the uninvoiced receipts account
1272
Map input = UtilMisc.toMap("glAccountId", uninvoicedGlAccountId, "organizationPartyId", organizationPartyId, "partyId", originPartyId);
1273            input.put("productId", productId);
1274            input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1275            input.put("acctgTransEntryTypeId", "_NA_");
1276            input.put("debitCreditFlag", "C");
1277            input.put("acctgTransEntrySeqId", Integer.toString(0));
1278            input.put("roleTypeId", "BILL_FROM_VENDOR");
1279            GenericValue creditAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1280
1281            // Transaction to debit the inventory GL acct
1282
input = UtilMisc.toMap("glAccountId", inventoryGlAccount, "organizationPartyId", organizationPartyId, "partyId", originPartyId);
1283            input.put("productId", productId);
1284            input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1285            input.put("acctgTransEntryTypeId", "_NA_");
1286            input.put("debitCreditFlag", "D");
1287            input.put("acctgTransEntrySeqId", Integer.toString(1));
1288            input.put("roleTypeId", "BILL_FROM_VENDOR");
1289            GenericValue debitAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1290
1291            // Perform the transaction
1292
input = UtilMisc.toMap("transactionDate", UtilDateTime.nowTimestamp(), "partyId", originPartyId, "userLogin", userLogin);
1293            input.put("acctgTransEntries", UtilMisc.toList(creditAcctTrans, debitAcctTrans));
1294            input.put("glFiscalTypeId", "ACTUAL");
1295            input.put("acctgTransTypeId", "OBLIGATION_ACCTG_TRA");
1296            input.put("receiptId", receiptId);
1297            if (shipmentId != null) {
1298                input.put("shipmentId", shipmentId);
1299            }
1300            Map servResult = dispatcher.runSync("createAcctgTransAndEntries", input);
1301
1302            if (((String JavaDoc) servResult.get(ModelService.RESPONSE_MESSAGE)).equals(ModelService.RESPOND_SUCCESS)) {
1303                Map result = ServiceUtil.returnSuccess();
1304                result.put("acctgTransId", servResult.get("acctgTransId"));
1305                return(result);
1306            } else {
1307                return servResult;
1308            }
1309        } catch (GenericEntityException ex) {
1310            return(ServiceUtil.returnError(ex.getMessage()));
1311        } catch (GenericServiceException ex) {
1312            return(ServiceUtil.returnError(ex.getMessage()));
1313        }
1314    }
1315
1316    /**
1317     * Service to post an inventory transaction to GL to record the change of the owner of the inventory item.
1318     * @param inventoryItemId
1319     * @param oldOwnerPartyId
1320     * @return
1321     * @author Jacopo Cappellato
1322     */

1323    public static Map postInventoryItemOwnerChange(DispatchContext dctx, Map context) {
1324        GenericDelegator delegator = dctx.getDelegator();
1325        LocalDispatcher dispatcher = dctx.getDispatcher();
1326        GenericValue userLogin = (GenericValue) context.get("userLogin");
1327
1328        String JavaDoc inventoryItemId = (String JavaDoc) context.get("inventoryItemId");
1329        String JavaDoc originOwnerPartyId = (String JavaDoc) context.get("oldOwnerPartyId");
1330        try {
1331            Map tmpMap = new HashMap();
1332
1333            GenericValue inventoryItem = delegator.findByPrimaryKey("InventoryItem",
1334                                                                    UtilMisc.toMap("inventoryItemId", inventoryItemId));
1335            if (inventoryItem == null) {
1336                return ServiceUtil.returnError("No InventoryItem entity record for inventoryItemId " + inventoryItemId);
1337            }
1338            String JavaDoc destinationOwnerPartyId = inventoryItem.getString("ownerPartyId");
1339            String JavaDoc productId = inventoryItem.getString("productId");
1340            // get the inventory item unit cost
1341
BigDecimal JavaDoc unitCost = inventoryItem.getBigDecimal("unitCost");
1342            // get the currency conversion factors
1343
BigDecimal JavaDoc originConversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, originOwnerPartyId, inventoryItem.getString("currencyUomId")));
1344            BigDecimal JavaDoc destinationConversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, destinationOwnerPartyId, inventoryItem.getString("currencyUomId")));
1345            // get the inventory item qty
1346
BigDecimal JavaDoc quantity = inventoryItem.getBigDecimal("quantityOnHandTotal");
1347            // The transaction amount is: amount = quantity * unitCost
1348
BigDecimal JavaDoc originTransactionAmount = unitCost.multiply(originConversionFactor).multiply(quantity).setScale(decimals, rounding);
1349            BigDecimal JavaDoc destinationTransactionAmount = unitCost.multiply(destinationConversionFactor).multiply(quantity).setScale(decimals, rounding);
1350            // Inventory and inventory xfer GL accounts
1351
String JavaDoc originInvGlAcctId = UtilAccounting.getProductOrgGlAccountId(productId, "INVENTORY_ACCOUNT", originOwnerPartyId, delegator);
1352            String JavaDoc destinationInvGlAcctId = UtilAccounting.getProductOrgGlAccountId(productId, "INVENTORY_ACCOUNT", destinationOwnerPartyId, delegator);
1353            String JavaDoc originInvXferGlAcctId = UtilAccounting.getProductOrgGlAccountId(productId, "INVENTORY_XFER_OUT", originOwnerPartyId, delegator);
1354            String JavaDoc destinationInvXferGlAcctId = UtilAccounting.getProductOrgGlAccountId(productId, "INVENTORY_XFER_IN", destinationOwnerPartyId, delegator);
1355
1356            // ===========================================================
1357
List transEntries = new ArrayList();
1358            // Transactions for the destination owner
1359
// Transaction to credit the inventory xfer in GL acct
1360
tmpMap = UtilMisc.toMap("glAccountId", destinationInvXferGlAcctId, "debitCreditFlag", "C",
1361                "amount", new Double JavaDoc(destinationTransactionAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(0),
1362                "organizationPartyId", destinationOwnerPartyId, "acctgTransEntryTypeId", "_NA_");
1363            tmpMap.put("productId", productId);
1364            transEntries.add(delegator.makeValue("AcctgTransEntry", tmpMap));
1365
1366            // Transaction to debit the inventory GL acct
1367
tmpMap = UtilMisc.toMap("glAccountId", destinationInvGlAcctId, "debitCreditFlag", "D",
1368                "amount", new Double JavaDoc(destinationTransactionAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(1),
1369                "organizationPartyId", destinationOwnerPartyId, "acctgTransEntryTypeId", "_NA_");
1370            tmpMap.put("productId", productId);
1371            transEntries.add(delegator.makeValue("AcctgTransEntry", tmpMap));
1372
1373            // Transactions for the origin owner
1374
// Transaction to credit the inventory GL acct
1375
tmpMap = UtilMisc.toMap("glAccountId", originInvGlAcctId, "debitCreditFlag", "C",
1376                "amount", new Double JavaDoc(originTransactionAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(0),
1377                "organizationPartyId", originOwnerPartyId, "acctgTransEntryTypeId", "_NA_");
1378            tmpMap.put("productId", productId);
1379            transEntries.add(delegator.makeValue("AcctgTransEntry", tmpMap));
1380
1381            // Transaction to debit the inventory xfer out GL acct
1382
tmpMap = UtilMisc.toMap("glAccountId", originInvXferGlAcctId, "debitCreditFlag", "D",
1383                "amount", new Double JavaDoc(originTransactionAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(1),
1384                "organizationPartyId", originOwnerPartyId, "acctgTransEntryTypeId", "_NA_");
1385            tmpMap.put("productId", productId);
1386            transEntries.add(delegator.makeValue("AcctgTransEntry", tmpMap));
1387
1388            // Get origin owner's party COGS method. If method is COGS_AVG_COST, also compute the inventory adjustment amount = (prodAvgCost - unitCost) * quantity
1389
GenericValue acctgPref = delegator.findByPrimaryKeyCache("PartyAcctgPreference", UtilMisc.toMap("partyId", originOwnerPartyId));
1390            String JavaDoc cogsMethodId = acctgPref.getString("cogsMethodId");
1391            BigDecimal JavaDoc inventoryAdjAmount = null;
1392            if ((cogsMethodId != null) && (cogsMethodId.equals("COGS_AVG_COST"))) {
1393                BigDecimal JavaDoc prodAvgCost = UtilCOGS.getProductAverageCost(productId, originOwnerPartyId, userLogin, delegator, dispatcher);
1394                if (prodAvgCost == null) return ServiceUtil.returnError("Unable to find a product average cost for product ["+productId+"] in organization ["+originOwnerPartyId+"]");
1395                inventoryAdjAmount = prodAvgCost.subtract(unitCost.multiply(originConversionFactor)).multiply(quantity).setScale(decimals, rounding);
1396            }
1397            // Adjustment accounting transaction
1398
if ((inventoryAdjAmount != null) && (inventoryAdjAmount.compareTo(ZERO) != 0)) {
1399                // GL accounts for cost adjustments due to Average Cost. Right now we're going to use a separate INVENTORY AVG_COST adjustment, but the COGS adjustment just goes to COGS
1400
String JavaDoc invGlAcctAdjId = UtilAccounting.getProductOrgGlAccountId(productId, "INV_ADJ_AVG_COST", originOwnerPartyId, delegator);
1401                // Transaction to credit the adj inventory GL acct
1402
tmpMap = UtilMisc.toMap("glAccountId", invGlAcctAdjId, "debitCreditFlag", "C",
1403                    "amount", new Double JavaDoc(inventoryAdjAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(0),
1404                    "organizationPartyId", originOwnerPartyId, "acctgTransEntryTypeId", "_NA_");
1405                tmpMap.put("productId", productId);
1406                transEntries.add(delegator.makeValue("AcctgTransEntry", tmpMap));
1407
1408                // Transaction to debit the adj cogs GL acct
1409
tmpMap = UtilMisc.toMap("glAccountId", originInvXferGlAcctId, "debitCreditFlag", "D",
1410                    "amount", new Double JavaDoc(inventoryAdjAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(1),
1411                    "organizationPartyId", originOwnerPartyId, "acctgTransEntryTypeId", "_NA_");
1412                tmpMap.put("productId", productId);
1413                transEntries.add(delegator.makeValue("AcctgTransEntry", tmpMap));
1414            }
1415
1416            // Perform the transaction
1417
tmpMap = UtilMisc.toMap("acctgTransEntries", transEntries,
1418                    "glFiscalTypeId", "ACTUAL", "acctgTransTypeId", "ITEM_VARIANCE_ACCTG_",
1419                    "transactionDate", UtilDateTime.nowTimestamp(),
1420                    "userLogin", userLogin);
1421            tmpMap.put("inventoryItemId", inventoryItemId);
1422            //tmpMap.put("inventoryTransferId", inventoryTransferId);
1423
tmpMap = dispatcher.runSync("createAcctgTransAndEntries", tmpMap);
1424
1425            if (ServiceUtil.isError(tmpMap)) {
1426                return tmpMap;
1427            }
1428            
1429            // Products average costs are updated for the destination owner party id.
1430
tmpMap = dispatcher.runSync("updateProductAverageCost",
1431                                        UtilMisc.toMap("organizationPartyId", destinationOwnerPartyId,
1432                                                       "productId", productId,
1433                                                       "userLogin", userLogin));
1434            if (ServiceUtil.isError(tmpMap)) {
1435                return tmpMap;
1436            }
1437
1438            Map result = ServiceUtil.returnSuccess();
1439            result.put("acctgTransId", tmpMap.get("acctgTransId"));
1440            return result;
1441
1442        } catch (GenericEntityException ex) {
1443            return(ServiceUtil.returnError(ex.getMessage()));
1444        } catch (GenericServiceException ex) {
1445            return(ServiceUtil.returnError(ex.getMessage()));
1446        }
1447    }
1448    /**
1449     * Posts raw materials inventory issuances (to a WorkEffort) to the GL:
1450     * Debit WIP Inventory, Credit Raw Materials Inventory
1451     * @author Jacopo Cappellato
1452     */

1453    public static Map postRawMaterialIssuancesToGl(DispatchContext dctx, Map context) {
1454        GenericDelegator delegator = dctx.getDelegator();
1455        LocalDispatcher dispatcher = dctx.getDispatcher();
1456        GenericValue userLogin = (GenericValue) context.get("userLogin");
1457
1458        String JavaDoc workEffortId = (String JavaDoc) context.get("workEffortId");
1459        String JavaDoc finishedProductId = (String JavaDoc) context.get("finishedProductId");
1460        try {
1461            GenericValue workEffort = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", workEffortId));
1462            if (finishedProductId == null) {
1463                // The finished product is retrieved from the production run
1464
GenericValue productionRun = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", workEffort.getString("workEffortParentId")));
1465                GenericValue finishedProduct = EntityUtil.getFirst(productionRun.getRelated("WorkEffortGoodStandard", UtilMisc.toMap("workEffortGoodStdTypeId", "PRUN_PROD_DELIV"), null));
1466                finishedProductId = finishedProduct.getString("productId");
1467            }
1468            GenericValue facility = workEffort.getRelatedOne("Facility");
1469            if (facility == null) {
1470                return ServiceUtil.returnError("Could not find the facility for work effort [" + workEffortId + "]");
1471            }
1472            String JavaDoc ownerPartyId = facility.getString("ownerPartyId");
1473
1474            List issuances = delegator.findByAnd("WorkEffortInventoryAssign", UtilMisc.toMap("workEffortId", workEffortId));
1475            Iterator issuancesIt = issuances.iterator();
1476            List transEntries = new ArrayList();
1477            Map input = null;
1478            while (issuancesIt.hasNext()) {
1479                GenericValue itemIssuance = (GenericValue)issuancesIt.next();
1480                GenericValue inventoryItem = itemIssuance.getRelatedOne("InventoryItem");
1481                String JavaDoc productId = inventoryItem.getString("productId");
1482                BigDecimal JavaDoc quantityIssued = itemIssuance.getBigDecimal("quantity");
1483                // get the inventory item's owner
1484
String JavaDoc inventoryOwnerPartyId = inventoryItem.getString("ownerPartyId");
1485                if (!inventoryOwnerPartyId.equals(ownerPartyId)) {
1486                    return ServiceUtil.returnError("The inventory item [" + inventoryOwnerPartyId +
1487                                                   "] is not owned by the owner of the facility [" + facility.getString("facilityId") + "] in item issuance [" + itemIssuance.getString("itemIssuanceId") + "]");
1488                }
1489                // get the inventory item unit cost
1490
BigDecimal JavaDoc unitCost = inventoryItem.getBigDecimal("unitCost");
1491                // get the currency conversion factor
1492
BigDecimal JavaDoc conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, ownerPartyId, inventoryItem.getString("currencyUomId")));
1493                // validate the unitCost and compute transaction amount
1494
if (unitCost == null) {
1495                    return ServiceUtil.returnError("Could not determine unitCost of product [" + productId +
1496                                                   "] for item issuance [" + itemIssuance.getString("itemIssuanceId") + "]");
1497                }
1498                // convert the intentory item's unit cost into the owner's currency
1499
unitCost = unitCost.multiply(conversionFactor).setScale(decimals, rounding);
1500                BigDecimal JavaDoc transactionAmount = unitCost.multiply(quantityIssued).setScale(decimals, rounding);
1501                // Inventory GL account
1502
String JavaDoc creditAccountId = UtilAccounting.getProductOrgGlAccountId(productId, "RAWMAT_INVENTORY", ownerPartyId, delegator);
1503                String JavaDoc debitAccountId = UtilAccounting.getProductOrgGlAccountId(productId, "WIP_INVENTORY", ownerPartyId, delegator);
1504                
1505                // Transaction to credit the inventory account
1506
input = UtilMisc.toMap("glAccountId", creditAccountId, "organizationPartyId", ownerPartyId);
1507                input.put("productId", productId);
1508                input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1509                input.put("acctgTransEntryTypeId", "_NA_");
1510                input.put("debitCreditFlag", "C");
1511                input.put("acctgTransEntrySeqId", Integer.toString(0));
1512                GenericValue creditAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1513                transEntries.add(creditAcctTrans);
1514                
1515                // Transaction to debit the Wip account
1516
input = UtilMisc.toMap("glAccountId", debitAccountId, "organizationPartyId", ownerPartyId);
1517                input.put("productId", finishedProductId);
1518                input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1519                input.put("acctgTransEntryTypeId", "_NA_");
1520                input.put("debitCreditFlag", "D");
1521                input.put("acctgTransEntrySeqId", Integer.toString(1));
1522                GenericValue debitAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1523                transEntries.add(debitAcctTrans);
1524
1525                // Get owner's party COGS method. If method is COGS_AVG_COST, also compute the inventory adjustment amount = (prodAvgCost - unitCost) * quantityIssued
1526
GenericValue acctgPref = delegator.findByPrimaryKeyCache("PartyAcctgPreference", UtilMisc.toMap("partyId", ownerPartyId));
1527                String JavaDoc cogsMethodId = acctgPref.getString("cogsMethodId");
1528                BigDecimal JavaDoc inventoryAdjAmount = null;
1529                if ((cogsMethodId != null) && (cogsMethodId.equals("COGS_AVG_COST"))) {
1530                    BigDecimal JavaDoc prodAvgCost = UtilCOGS.getProductAverageCost(productId, ownerPartyId, userLogin, delegator, dispatcher);
1531                    if (prodAvgCost == null) return ServiceUtil.returnError("Unable to find a product average cost for product ["+productId+"] in organization ["+ownerPartyId+"]");
1532                    // TODO: there could be rounding issues here; maybe it's better to do something like this:
1533
// (prodAvgCost - unitCost) * quantityOnHandVar and then set the scale.
1534
inventoryAdjAmount = prodAvgCost.subtract(unitCost).multiply(quantityIssued).setScale(decimals, rounding);
1535                }
1536                // Adjustment for difference Average Cost Inventory
1537
if ((inventoryAdjAmount != null) && (inventoryAdjAmount.compareTo(ZERO) != 0)) {
1538                    String JavaDoc debitGlAcctId = UtilAccounting.getProductOrgGlAccountId(productId, "WIP_INVENTORY", ownerPartyId, delegator);
1539                    String JavaDoc creditGlAcctId = UtilAccounting.getProductOrgGlAccountId(productId, "RAWMAT_INVENTORY", ownerPartyId, delegator);
1540                    // Transaction to credit the raw materials account
1541
input = UtilMisc.toMap("glAccountId", creditGlAcctId, "debitCreditFlag", "C",
1542                        "amount", new Double JavaDoc(inventoryAdjAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(0),
1543                        "organizationPartyId", ownerPartyId, "acctgTransEntryTypeId", "_NA_");
1544                    input.put("productId", productId);
1545                    transEntries.add(delegator.makeValue("AcctgTransEntry", input));
1546
1547                    // Transaction to debit the work in progress account
1548
input = UtilMisc.toMap("glAccountId", debitGlAcctId, "debitCreditFlag", "D",
1549                        "amount", new Double JavaDoc(inventoryAdjAmount.doubleValue()), "acctgTransEntrySeqId", Integer.toString(1),
1550                        "organizationPartyId", ownerPartyId, "acctgTransEntryTypeId", "_NA_");
1551                    input.put("productId", finishedProductId);
1552                    transEntries.add(delegator.makeValue("AcctgTransEntry", input));
1553                }
1554 
1555            }
1556            // Perform the transaction
1557
input = UtilMisc.toMap("transactionDate", UtilDateTime.nowTimestamp(), "userLogin", userLogin);
1558            input.put("acctgTransEntries", transEntries);
1559            input.put("glFiscalTypeId", "ACTUAL");
1560            input.put("acctgTransTypeId", "OBLIGATION_ACCTG_TRA");
1561            input.put("workEffortId", workEffortId);
1562
1563            Map servResult = dispatcher.runSync("createAcctgTransAndEntries", input);
1564
1565            if (ServiceUtil.isError(servResult)) {
1566                return servResult;
1567            }
1568
1569            Map result = ServiceUtil.returnSuccess();
1570            result.put("acctgTransId", servResult.get("acctgTransId"));
1571
1572        } catch (GenericEntityException ex) {
1573            return(ServiceUtil.returnError(ex.getMessage()));
1574        } catch (GenericServiceException ex) {
1575            return(ServiceUtil.returnError(ex.getMessage()));
1576        }
1577        
1578        return ServiceUtil.returnSuccess();
1579    }
1580
1581    /**
1582     * Posts direct and indirect production run task costs:
1583     * Debit WIP Inventory, Credit (some kind of cost account)
1584     * @author Jacopo Cappellato
1585     */

1586    public static Map postWorkEffortCostsToGl(DispatchContext dctx, Map context) {
1587        GenericDelegator delegator = dctx.getDelegator();
1588        LocalDispatcher dispatcher = dctx.getDispatcher();
1589        GenericValue userLogin = (GenericValue) context.get("userLogin");
1590
1591        String JavaDoc workEffortId = (String JavaDoc) context.get("workEffortId");
1592        try {
1593            GenericValue workEffort = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", workEffortId));
1594            GenericValue productionRun = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", workEffort.getString("workEffortParentId")));
1595            GenericValue finishedProduct = EntityUtil.getFirst(productionRun.getRelated("WorkEffortGoodStandard", UtilMisc.toMap("workEffortGoodStdTypeId", "PRUN_PROD_DELIV"), null));
1596            GenericValue facility = workEffort.getRelatedOne("Facility");
1597            if (facility == null) {
1598                return ServiceUtil.returnError("Could not find the facility for work effort [" + workEffortId + "]");
1599            }
1600            String JavaDoc ownerPartyId = facility.getString("ownerPartyId");
1601
1602            List costComponents = EntityUtil.filterByDate(delegator.findByAnd("CostComponent", UtilMisc.toMap("workEffortId", workEffortId)));
1603            Iterator costComponentsIt = costComponents.iterator();
1604            List transEntries = new ArrayList();
1605            Map input = null;
1606            while (costComponentsIt.hasNext()) {
1607                GenericValue costComponent = (GenericValue)costComponentsIt.next();
1608                if (!"ACTUAL_MAT_COST".equals(costComponent.getString("costComponentTypeId"))) {
1609                    GenericValue costComponentCalc = costComponent.getRelatedOne("CostComponentCalc");
1610                    BigDecimal JavaDoc cost = costComponent.getBigDecimal("cost");
1611                    if (cost.compareTo(ZERO) != 0) {
1612                        String JavaDoc creditAccountTypeId = costComponentCalc.getString("costGlAccountTypeId");
1613                        String JavaDoc debitAccountTypeId = costComponentCalc.getString("offsettingGlAccountTypeId");
1614                        if (debitAccountTypeId == null) {
1615                            debitAccountTypeId = "WIP_INVENTORY";
1616                        }
1617                        String JavaDoc creditAccountId = UtilAccounting.getDefaultAccountId(creditAccountTypeId, ownerPartyId, delegator);
1618                        String JavaDoc debitAccountId = UtilAccounting.getDefaultAccountId(debitAccountTypeId, ownerPartyId, delegator);
1619                        // get the currency conversion factor
1620
BigDecimal JavaDoc conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, ownerPartyId, costComponent.getString("costUomId")));
1621                        // convert the cost into the owner's currency
1622
BigDecimal JavaDoc transactionAmount = cost.multiply(conversionFactor).setScale(decimals, rounding);
1623
1624                        // Transaction to credit the inventory account
1625
input = UtilMisc.toMap("glAccountId", creditAccountId, "organizationPartyId", ownerPartyId);
1626                        input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1627                        input.put("acctgTransEntryTypeId", "_NA_");
1628                        input.put("debitCreditFlag", "C");
1629                        input.put("acctgTransEntrySeqId", Integer.toString(0));
1630                        GenericValue creditAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1631                        transEntries.add(creditAcctTrans);
1632
1633                        // Transaction to debit the Wip account
1634
input = UtilMisc.toMap("glAccountId", debitAccountId, "organizationPartyId", ownerPartyId);
1635                        input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1636                        input.put("acctgTransEntryTypeId", "_NA_");
1637                        input.put("debitCreditFlag", "D");
1638                        input.put("productId", finishedProduct.getString("productId"));
1639                        input.put("acctgTransEntrySeqId", Integer.toString(1));
1640                        GenericValue debitAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1641                        transEntries.add(debitAcctTrans);
1642                    }
1643                }
1644            }
1645            // Perform the transaction
1646
input = UtilMisc.toMap("transactionDate", UtilDateTime.nowTimestamp(), "userLogin", userLogin);
1647            input.put("acctgTransEntries", transEntries);
1648            input.put("glFiscalTypeId", "ACTUAL");
1649            input.put("acctgTransTypeId", "OBLIGATION_ACCTG_TRA");
1650            input.put("workEffortId", workEffortId);
1651
1652            Map servResult = dispatcher.runSync("createAcctgTransAndEntries", input);
1653
1654            if (ServiceUtil.isError(servResult)) {
1655                return servResult;
1656            }
1657
1658            Map result = ServiceUtil.returnSuccess();
1659            result.put("acctgTransId", servResult.get("acctgTransId"));
1660
1661        } catch (GenericEntityException ex) {
1662            return(ServiceUtil.returnError(ex.getMessage()));
1663        } catch (GenericServiceException ex) {
1664            return(ServiceUtil.returnError(ex.getMessage()));
1665        }
1666        
1667        return ServiceUtil.returnSuccess();
1668    }
1669
1670    /**
1671     * Posts inventory produced by a WorkEffort (production run) to the GL:
1672     * Debit Finished Goods Inventory, Credit WIP Inventory
1673     * @author Jacopo Cappellato
1674     */

1675    public static Map postInventoryProducedToGl(DispatchContext dctx, Map context) {
1676        GenericDelegator delegator = dctx.getDelegator();
1677        LocalDispatcher dispatcher = dctx.getDispatcher();
1678        GenericValue userLogin = (GenericValue) context.get("userLogin");
1679
1680        String JavaDoc workEffortId = (String JavaDoc)context.get("workEffortId");
1681        List inventoryItemIds = (List)context.get("inventoryItemIds");
1682        try {
1683            GenericValue workEffort = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", workEffortId));
1684            GenericValue facility = workEffort.getRelatedOne("Facility");
1685            if (facility == null) {
1686                return ServiceUtil.returnError("Could not find the facility for work effort [" + workEffortId + "]");
1687            }
1688            String JavaDoc ownerPartyId = facility.getString("ownerPartyId");
1689
1690            Iterator inventoryItemIdsIt = inventoryItemIds.iterator();
1691            List transEntries = new ArrayList();
1692            Map input = null;
1693            while (inventoryItemIdsIt.hasNext()) {
1694                String JavaDoc inventoryItemId = (String JavaDoc)inventoryItemIdsIt.next();
1695                GenericValue inventoryItem = delegator.findByPrimaryKey("InventoryItem", UtilMisc.toMap("inventoryItemId", inventoryItemId));
1696                BigDecimal JavaDoc unitCost = inventoryItem.getBigDecimal("unitCost");
1697                String JavaDoc productId = inventoryItem.getString("productId");
1698                if ((unitCost != null) && (unitCost.compareTo(ZERO) != 0) && (inventoryItem.get("quantityOnHandTotal") != null)) {
1699                    String JavaDoc creditAccountId = UtilAccounting.getProductOrgGlAccountId(productId, "WIP_INVENTORY", ownerPartyId, delegator);
1700                    // FIXME
1701
String JavaDoc debitAccountId = UtilAccounting.getProductOrgGlAccountId(productId, "INVENTORY_ACCOUNT", ownerPartyId, delegator);
1702                    // get the currency conversion factor
1703
BigDecimal JavaDoc conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, ownerPartyId, inventoryItem.getString("currencyUomId")));
1704                    // convert the cost into the owner's currency and get the total transaction amount
1705
BigDecimal JavaDoc transactionAmount = unitCost.multiply(conversionFactor).multiply(inventoryItem.getBigDecimal("quantityOnHandTotal")).setScale(decimals, rounding);
1706
1707                    // Transaction to credit the inventory account
1708
input = UtilMisc.toMap("glAccountId", creditAccountId, "organizationPartyId", ownerPartyId);
1709                    input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1710                    input.put("acctgTransEntryTypeId", "_NA_");
1711                    input.put("debitCreditFlag", "C");
1712                    input.put("productId", productId);
1713                    input.put("acctgTransEntrySeqId", Integer.toString(0));
1714                    GenericValue creditAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1715                    transEntries.add(creditAcctTrans);
1716
1717                    // Transaction to debit the Wip account
1718
input = UtilMisc.toMap("glAccountId", debitAccountId, "organizationPartyId", ownerPartyId);
1719                    input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
1720                    input.put("acctgTransEntryTypeId", "_NA_");
1721                    input.put("debitCreditFlag", "D");
1722                    input.put("productId", productId);
1723                    input.put("acctgTransEntrySeqId", Integer.toString(1));
1724                    GenericValue debitAcctTrans = delegator.makeValue("AcctgTransEntry", input);
1725                    transEntries.add(debitAcctTrans);
1726
1727                    // update the average cost of this inventory item
1728
Map tmpResult = dispatcher.runSync("updateProductAverageCost", UtilMisc.toMap("productId", inventoryItem.getString("productId"),
1729                            "organizationPartyId", inventoryItem.getString("ownerPartyId"), "userLogin", userLogin));
1730                }
1731            }
1732            // Perform the transaction
1733
input = UtilMisc.toMap("transactionDate", UtilDateTime.nowTimestamp(), "userLogin", userLogin);
1734            input.put("acctgTransEntries", transEntries);
1735            input.put("glFiscalTypeId", "ACTUAL");
1736            input.put("acctgTransTypeId", "OBLIGATION_ACCTG_TRA");
1737            input.put("workEffortId", workEffortId);
1738
1739            Map servResult = dispatcher.runSync("createAcctgTransAndEntries", input);
1740
1741            if (ServiceUtil.isError(servResult)) {
1742                return servResult;
1743            }
1744
1745            Map result = ServiceUtil.returnSuccess();
1746            result.put("acctgTransId", servResult.get("acctgTransId"));
1747
1748        } catch (GenericEntityException ex) {
1749            return(ServiceUtil.returnError(ex.getMessage()));
1750        } catch (GenericServiceException ex) {
1751            return(ServiceUtil.returnError(ex.getMessage()));
1752        }
1753        
1754        return ServiceUtil.returnSuccess();
1755    }
1756
1757    /**
1758     * Service to close out a particular time period for an organization.
1759     * @param dctx
1760     * @param context
1761     * @return
1762     */

1763    public static Map closeTimePeriod(DispatchContext dctx, Map context) {
1764        GenericDelegator delegator = dctx.getDelegator();
1765        LocalDispatcher dispatcher = dctx.getDispatcher();
1766        GenericValue userLogin = (GenericValue) context.get("userLogin");
1767
1768        String JavaDoc organizationPartyId = (String JavaDoc) context.get("organizationPartyId");
1769        String JavaDoc customTimePeriodId = (String JavaDoc) context.get("customTimePeriodId");
1770
1771        try {
1772            // figure out the current time period's type (year, quarter, month)
1773
GenericValue timePeriod = delegator.findByPrimaryKeyCache("CustomTimePeriod", UtilMisc.toMap("customTimePeriodId", customTimePeriodId));
1774            if (timePeriod == null) {
1775                return ServiceUtil.returnError("Cannot find a time period for " + organizationPartyId + " and time period id " + customTimePeriodId);
1776            }
1777            String JavaDoc periodTypeId = timePeriod.getString("periodTypeId");
1778
1779            // get the organization's accounts for profit and loss (net income) and retained earnings
1780
String JavaDoc retainedEarningsGlAccountId = UtilAccounting.getProductOrgGlAccountId(null, "RETAINED_EARNINGS", organizationPartyId, delegator);
1781            String JavaDoc profitLossGlAccountId = UtilAccounting.getProductOrgGlAccountId(null, "PROFIT_LOSS_ACCOUNT", organizationPartyId, delegator);
1782
1783            // figure out the profit and loss since the last closed time period
1784
double netIncome = 0.0;
1785            Map tmpResult = dispatcher.runSync("getActualNetIncomeSinceLastClosing", UtilMisc.toMap("organizationPartyId", organizationPartyId, "periodTypeId", periodTypeId,
1786                        "thruDate", UtilDateTime.toTimestamp(timePeriod.getDate("thruDate")), "userLogin", userLogin));
1787            if (tmpResult.get("netIncome") != null) {
1788                netIncome = ((Double JavaDoc) tmpResult.get("netIncome")).doubleValue();
1789            } else {
1790               return ServiceUtil.returnError("Cannot calculate a net income for" + organizationPartyId + " and time period id " + customTimePeriodId);
1791            }
1792
1793            // make sure that the posted net income and retained earnings of this time period are correct (vs the calculated numbers). If not, we'll need to create accounting transaction entries
1794
GenericValue postedNetIncome = delegator.findByPrimaryKey("GlAccountHistory", UtilMisc.toMap("organizationPartyId", organizationPartyId,
1795                 "customTimePeriodId", timePeriod.getString("customTimePeriodId"), "glAccountId", profitLossGlAccountId));
1796
1797            if (postedNetIncome == null) {
1798                // no earnings and net income have been posted to this period, we'll have to create acctg transaction entries
1799
GenericValue retainedEarningsTransaction = delegator.makeValue("AcctgTransEntry", UtilMisc.toMap("glAccountId", retainedEarningsGlAccountId, "debitCreditFlag", "C",
1800                      "amount", new Double JavaDoc(netIncome), "acctgTransEntrySeqId", Integer.toString(0), "organizationPartyId", organizationPartyId, "acctgTransEntryTypeId", "_NA_"));
1801                GenericValue netIncomeTransaction = delegator.makeValue("AcctgTransEntry", UtilMisc.toMap("glAccountId", profitLossGlAccountId, "debitCreditFlag", "D",
1802                      "amount", new Double JavaDoc(netIncome), "acctgTransEntrySeqId", Integer.toString(1), "organizationPartyId", organizationPartyId, "acctgTransEntryTypeId", "_NA_"));
1803
1804                // this is subtle - the transactionDate must be right before the thruDate, or the transaction will actually overlap into the next time period. We subtract 1 second (1000 milliseconds) to move it before
1805
tmpResult = dispatcher.runSync("createAcctgTransAndEntries", UtilMisc.toMap("acctgTransEntries", UtilMisc.toList(retainedEarningsTransaction, netIncomeTransaction),
1806                      "glFiscalTypeId", "ACTUAL", "transactionDate", new Timestamp JavaDoc(timePeriod.getDate("thruDate").getTime() - 1000), "acctgTransTypeId", "PERIOD_CLOSING", "userLogin", userLogin));
1807            } else if (Math.abs(UtilAccounting.getNetBalance(postedNetIncome, module).doubleValue() - netIncome) < epsilon) {
1808                // this is ok too - send a message to the user
1809
Debug.logInfo("Net income and earnings already posted. Not posting again", module);
1810            } else {
1811                // this is bad. somehow, earnings have been posted but don't add up
1812
return ServiceUtil.returnError("Calculated a net income of " + netIncome + " but found posted net income of " + UtilAccounting.getNetBalance(postedNetIncome, module));
1813            }
1814
1815            // find the previous closed time period, in case we need to carry a balance forward
1816
String JavaDoc lastClosedTimePeriodId = null;
1817            tmpResult = dispatcher.runSync("findLastClosedDate", UtilMisc.toMap("organizationPartyId", organizationPartyId,
1818                      "periodTypeId", timePeriod.getString("periodTypeId"), "userLogin", userLogin));
1819            if (tmpResult.get("lastClosedTimePeriod") != null) {
1820                 lastClosedTimePeriodId = ((GenericValue) tmpResult.get("lastClosedTimePeriod")).getString("customTimePeriodId");
1821            }
1822
1823            // now set the ending balance for all GlAccountHistory entries for this time period and organization
1824
// bringing forward gl account history from previous time period for ASSET, LIABILITY, and EQUITY accounts
1825
// we do this by creating a Map of the current period's GlAccountHistory and then adding in those from the previous period
1826

1827            // the first step is to find all GlAccountHistory of the current time period
1828

1829            List glAccountHistories = delegator.findByAnd("GlAccountHistory", UtilMisc.toMap("organizationPartyId", organizationPartyId,
1830                            "customTimePeriodId", timePeriod.getString("customTimePeriodId")));
1831
1832            // now make a map of glAccountId -> GlAccountHistory, with the correct endingBalance in the GlAccountHistory values
1833
Map updatedGlAccountHistories = new HashMap();
1834            for (Iterator gAHi = glAccountHistories.iterator(); gAHi.hasNext(); ) {
1835                GenericValue glAccountHistory = (GenericValue) gAHi.next();
1836                double netBalance = UtilAccounting.getNetBalance(glAccountHistory, module).doubleValue();
1837                glAccountHistory.set("endingBalance", new Double JavaDoc(netBalance));
1838                updatedGlAccountHistories.put(glAccountHistory.getString("glAccountId"), glAccountHistory);
1839            }
1840
1841            // is there a previously closed time period? If so, then find all of its GlAccountHistory and add their ending balances in
1842
if (lastClosedTimePeriodId != null) {
1843                // find the previous period GL account histories. We are ONLY carrying forward ASSET, LIABILITY, EQUITY accounts
1844
EntityCondition previousPeriodConditions = new EntityConditionList(UtilMisc.toList(new EntityExpr("organizationPartyId", EntityOperator.EQUALS, organizationPartyId),
1845                          new EntityConditionList(UtilMisc.toList(
1846                                  UtilFinancial.getAssetExpr(delegator),
1847                                  UtilFinancial.getLiabilityExpr(delegator),
1848                                  UtilFinancial.getEquityExpr(delegator)),
1849                          EntityOperator.OR),
1850                   new EntityExpr("customTimePeriodId", EntityOperator.EQUALS, lastClosedTimePeriodId)), EntityOperator.AND);
1851
1852                List previousGlAccountHistories = delegator.findByCondition("GlAccountAndHistory", previousPeriodConditions,
1853                    UtilMisc.toList("organizationPartyId", "customTimePeriodId", "glAccountId", "postedDebits", "postedCredits", "endingBalance"), UtilMisc.toList("glAccountId"));
1854
1855                // loop and check them against current period
1856
for (Iterator pGAHi = previousGlAccountHistories.iterator(); pGAHi.hasNext(); ) {
1857                    GenericValue previousGlAccountAndHistory = (GenericValue) pGAHi.next();
1858                    GenericValue previousGlAccountHistory = previousGlAccountAndHistory.getRelatedOne("GlAccountHistory");
1859                    double previousNetBalance = UtilAccounting.getNetBalance(previousGlAccountHistory, module).doubleValue();
1860
1861                    // is this gl account also in the current period?
1862
if (updatedGlAccountHistories.get(previousGlAccountAndHistory.getString("glAccountId")) != null) {
1863                        // yes: carry forward the balance
1864
GenericValue updatedGlAccountHistory = (GenericValue) updatedGlAccountHistories.get(previousGlAccountAndHistory.getString("glAccountId"));
1865                        double newEndingBalance = updatedGlAccountHistory.getDouble("endingBalance").doubleValue() + previousGlAccountHistory.getDouble("endingBalance").doubleValue();
1866                        updatedGlAccountHistory.set("endingBalance", new Double JavaDoc(newEndingBalance));
1867                    } else {
1868                        // no: put it in with the previous period's balance but the current period's time period id
1869
GenericValue carriedForwardGlAccountHistory = delegator.makeValue("GlAccountHistory", UtilMisc.toMap("glAccountId", previousGlAccountHistory.getString("glAccountId"),
1870                               "organizationPartyId", organizationPartyId, "customTimePeriodId", timePeriod.getString("customTimePeriodId"),
1871                               "postedDebits", new Double JavaDoc(0.0), "postedCredits", new Double JavaDoc(0.0), "endingBalance", previousGlAccountHistory.getDouble("endingBalance")));
1872                        updatedGlAccountHistories.put(previousGlAccountHistory.getString("glAccountId"), carriedForwardGlAccountHistory);
1873                    }
1874                }
1875            }
1876
1877            // store all of these
1878
List toBeStored = new ArrayList();
1879            for (Iterator vi = updatedGlAccountHistories.values().iterator(); vi.hasNext(); ) {
1880                toBeStored.add(vi.next());
1881            }
1882            delegator.storeAll(toBeStored);
1883
1884            // finally, set time period to closed
1885
tmpResult = dispatcher.runSync("updateCustomTimePeriod", UtilMisc.toMap("customTimePeriodId", timePeriod.getString("customTimePeriodId"), "organizationPartyId", organizationPartyId,
1886                "isClosed", "Y", "userLogin", userLogin));
1887            
1888            return(ServiceUtil.returnSuccess());
1889    
1890        } catch (GenericEntityException ex) {
1891            return(ServiceUtil.returnError(ex.getMessage()));
1892        } catch (GenericServiceException ex) {
1893            return(ServiceUtil.returnError(ex.getMessage()));
1894        }
1895    }
1896
1897    /**
1898     * Service closes all time periods which end on the thruDate of the specified time period
1899     * @param dctx
1900     * @param context
1901     * @return
1902     */

1903    public static Map closeAllTimePeriods(DispatchContext dctx, Map context) {
1904        GenericDelegator delegator = dctx.getDelegator();
1905        LocalDispatcher dispatcher = dctx.getDispatcher();
1906        GenericValue userLogin = (GenericValue) context.get("userLogin");
1907
1908        String JavaDoc organizationPartyId = (String JavaDoc) context.get("organizationPartyId");
1909        String JavaDoc customTimePeriodId = (String JavaDoc) context.get("customTimePeriodId");
1910
1911        try {
1912            GenericValue timePeriod = delegator.findByPrimaryKeyCache("CustomTimePeriod", UtilMisc.toMap("customTimePeriodId", customTimePeriodId));
1913            if (timePeriod == null) {
1914                return ServiceUtil.returnError("Cannot find a time period for " + organizationPartyId + " and time period id " + customTimePeriodId);
1915            }
1916            
1917            closeAllTimePeriodsByType(delegator, dispatcher, organizationPartyId, "FISCAL_WEEK", timePeriod.getDate("thruDate"), userLogin);
1918            closeAllTimePeriodsByType(delegator, dispatcher, organizationPartyId, "FISCAL_BIWEEK", timePeriod.getDate("thruDate"), userLogin);
1919            closeAllTimePeriodsByType(delegator, dispatcher, organizationPartyId, "FISCAL_MONTH", timePeriod.getDate("thruDate"), userLogin);
1920            closeAllTimePeriodsByType(delegator, dispatcher, organizationPartyId, "FISCAL_QUARTER", timePeriod.getDate("thruDate"), userLogin);
1921            closeAllTimePeriodsByType(delegator, dispatcher, organizationPartyId, "FISCAL_YEAR", timePeriod.getDate("thruDate"), userLogin);
1922            
1923            return(ServiceUtil.returnSuccess());
1924        } catch (GenericEntityException ex) {
1925           return(ServiceUtil.returnError(ex.getMessage()));
1926        } catch (GenericServiceException ex) {
1927           return(ServiceUtil.returnError(ex.getMessage()));
1928        }
1929    }
1930
1931    /**
1932     * Resets all the postedBalance of GlAccountOrganization for an organization to 0.0 for REVENUE, INCOME, and EXPENSE GL accounts.
1933     * @param dctx
1934     * @param context
1935     * @return
1936     */

1937    public static Map resetOrgGlAccountBalances(DispatchContext dctx, Map context) {
1938        GenericDelegator delegator = dctx.getDelegator();
1939        LocalDispatcher dispatcher = dctx.getDispatcher();
1940        GenericValue userLogin = (GenericValue) context.get("userLogin");
1941
1942        try {
1943            String JavaDoc organizationPartyId = (String JavaDoc) context.get("organizationPartyId");
1944            String JavaDoc customTimePeriodId = (String JavaDoc) context.get("customTimePeriodId");
1945            
1946            // find the REVENUE, EXPENSE, and INCOME gl accounts for the organization
1947
EntityCondition glAccountConditions = new EntityConditionList(UtilMisc.toList(new EntityExpr("organizationPartyId", EntityOperator.EQUALS, organizationPartyId),
1948                    new EntityConditionList(UtilMisc.toList(UtilFinancial.getGlAccountClassExpr("REVENUE", delegator),
1949                        UtilFinancial.getGlAccountClassExpr("EXPENSE", delegator),
1950                        UtilFinancial.getGlAccountClassExpr("INCOME", delegator)),
1951                    EntityOperator.OR),
1952                    new EntityExpr("postedBalance", EntityOperator.NOT_EQUAL, new Double JavaDoc(0.0))),
1953                EntityOperator.AND);
1954            List glAccounts = delegator.findByCondition("GlAccountOrganizationAndClass", glAccountConditions,
1955                    UtilMisc.toList("organizationPartyId", "glAccountId", "postedBalance"), UtilMisc.toList("glAccountId"));
1956            
1957            // Calculate the correct ending balances for these gl accounts, which is the sum of the transactions after the time period to be closed
1958
GenericValue timePeriod = delegator.findByPrimaryKeyCache("CustomTimePeriod", UtilMisc.toMap("customTimePeriodId", customTimePeriodId));
1959            Map tmpResult = dispatcher.runSync("getIncomeStatementAccountSumsByDate", UtilMisc.toMap("organizationPartyId", organizationPartyId,
1960                    "fromDate", UtilDateTime.toTimestamp(timePeriod.getDate("thruDate")), "thruDate", UtilDateTime.nowTimestamp(),
1961                    "glFiscalTypeId", "ACTUAL", "userLogin", userLogin));
1962            Map glAccountSums = new HashMap(); // Map of GlAccount -> Sum of transactions
1963
if (tmpResult.get("glAccountSums") != null) {
1964                glAccountSums = (Map) tmpResult.get("glAccountSums");
1965            }
1966            
1967            // reset all these accounts' posted balances to the sum of transactions after period being closed or, if there were no transactions, to zero
1968
for (Iterator gAi = glAccounts.iterator(); gAi.hasNext(); ) {
1969                GenericValue glAccount = ((GenericValue) gAi.next()).getRelatedOne("GlAccount");
1970                Double JavaDoc newAmount = null;
1971                if (glAccountSums.get(glAccount) != null) {
1972                    newAmount = (Double JavaDoc) glAccountSums.get(glAccount);
1973                } else {
1974                    newAmount = new Double JavaDoc (0.0);
1975                }
1976                Debug.logInfo("now getting ready to reset " + glAccount + " to " + newAmount, module);
1977                tmpResult = dispatcher.runSync("updateGlAccountOrganization", UtilMisc.toMap("organizationPartyId", organizationPartyId,
1978                        "glAccountId", glAccount.getString("glAccountId"), "postedBalance", newAmount, "userLogin", userLogin));
1979            }
1980                
1981            return(ServiceUtil.returnSuccess());
1982        } catch (GenericEntityException ex) {
1983           return(ServiceUtil.returnError(ex.getMessage()));
1984        } catch (GenericServiceException ex) {
1985           return(ServiceUtil.returnError(ex.getMessage()));
1986        }
1987    }
1988
1989    /**
1990     * Service to reconcile a GlAccount
1991     * @author Leon Torres
1992     */

1993    public static Map reconcileGlAccount(DispatchContext dctx, Map context) {
1994        GenericDelegator delegator = dctx.getDelegator();
1995        LocalDispatcher dispatcher = dctx.getDispatcher();
1996        GenericValue userLogin = (GenericValue) context.get("userLogin");
1997
1998        String JavaDoc organizationPartyId = (String JavaDoc) context.get("organizationPartyId");
1999        String JavaDoc glAccountId = (String JavaDoc) context.get("glAccountId");
2000        String JavaDoc glReconciliationName = (String JavaDoc) context.get("glReconciliationName");
2001        String JavaDoc description = (String JavaDoc) context.get("description");
2002        Double JavaDoc reconciledBalance = (Double JavaDoc) context.get("reconciledBalance");
2003        Timestamp JavaDoc reconciledDate = (Timestamp JavaDoc) context.get("reconciledDate");
2004        List acctgTransEntries = (List) context.get("acctgTransEntries");
2005        try {
2006            if (reconciledBalance == null) {
2007                return ServiceUtil.returnError("Cannot reconcile account " + glAccountId +": No reconciled balance found.");
2008            }
2009
2010            if (reconciledDate == null) {
2011                return ServiceUtil.returnError("Cannot reconcile account " + glAccountId +": No as of date specified.");
2012            }
2013
2014            if ((acctgTransEntries == null) || (acctgTransEntries.size() == 0)) {
2015                return ServiceUtil.returnError("Cannot reconcile account " + glAccountId +": No transactions to be reconciled.");
2016            }
2017
2018            // first, create a reconciliation
2019
Map params = UtilMisc.toMap("glAccountId", glAccountId, "organizationPartyId", organizationPartyId);
2020            params.put("glReconciliationName", glReconciliationName);
2021            params.put("description", description);
2022            params.put("reconciledBalance", reconciledBalance);
2023            params.put("reconciledDate", reconciledDate);
2024            params.put("userLogin", userLogin);
2025            Debug.logInfo(params.toString(), module);
2026            Map results = dispatcher.runSync("createGlReconciliation", params);
2027            if (ServiceUtil.isError(results)) {
2028                return results;
2029            }
2030            String JavaDoc glReconciliationId = (String JavaDoc) results.get("glReconciliationId");
2031
2032            /*
2033             * Our acctgTransId and acctgTransEntrySeqId were passed in joined together as acctgTransEntries
2034             * using the pipe | character as a delimiter. Here, we loop through these entries, split the IDs out
2035             * and 1) update each AcctgTransEntry, 2) create a GL Reconciliation entry
2036             */

2037            Iterator iter = acctgTransEntries.iterator();
2038            while (iter.hasNext()) {
2039                String JavaDoc entry = (String JavaDoc) iter.next();
2040                if (entry == null) continue;
2041
2042                // split the entry string into component transaction ids
2043
List tokens = StringUtil.split(entry, "|");
2044
2045                // continue if data isn't our pair of IDs
2046
if ((tokens == null) || (tokens.size() != 2)) continue;
2047                
2048                String JavaDoc acctgTransId = (String JavaDoc) tokens.get(0);
2049                String JavaDoc acctgTransEntrySeqId = (String JavaDoc) tokens.get(1);
2050
2051                // Grab our transaction entry
2052
GenericValue acctgTransEntry = delegator.findByPrimaryKey("AcctgTransEntry", UtilMisc.toMap("acctgTransId", acctgTransId, "acctgTransEntrySeqId", acctgTransEntrySeqId));
2053
2054                // to prevent doubleposts, we validate that we don't have AES_RECONCILED entries
2055
if (acctgTransEntry.getString("reconcileStatusId").equals("AES_RECONCILED")) {
2056                    return ServiceUtil.returnError("Cannot reconcile account: Submitted transaction entry is already reconciled (acctgTransId=" + acctgTransId
2057                            + ", acctgTransEntrySeqId=" + acctgTransEntrySeqId+ ").");
2058                }
2059
2060                // update AcctgTransEntry status to AES_RECONCILED
2061
params = UtilMisc.toMap("acctgTransId", acctgTransId, "acctgTransEntrySeqId", acctgTransEntrySeqId);
2062                params.put("userLogin", userLogin);
2063                params.put("reconcileStatusId", "AES_RECONCILED");
2064                results = dispatcher.runSync("updateAcctgTransEntry", params);
2065                if (ServiceUtil.isError(results)) {
2066                    throw new GenericServiceException("Failed to update AcctgTransEntry (acctgTransId=" + acctgTransId
2067                            + ", acctgTransEntrySeqId=" + acctgTransEntrySeqId+ ")");
2068                }
2069                
2070                // need amount of trans entry for gl reconcile entry
2071
Double JavaDoc amount = acctgTransEntry.getDouble("amount");
2072
2073                // prepare input for and call createGlReconciliationEntry
2074
params = UtilMisc.toMap("glReconciliationId", glReconciliationId);
2075                params.put("userLogin", userLogin);
2076                params.put("acctgTransId", acctgTransId);
2077                params.put("acctgTransEntrySeqId", acctgTransEntrySeqId);
2078                params.put("reconciledAmount", amount);
2079                results = dispatcher.runSync("createGlReconciliationEntry", params);
2080                if (ServiceUtil.isError(results)) {
2081                    throw new GenericServiceException("Failed to create GlReconciliationEntry (glReconciliationId=" + glReconciliationId
2082                            + ", acctgTransId=" + acctgTransId
2083                            + ", acctgTransEntrySeqId="+acctgTransEntrySeqId+")");
2084                }
2085            }
2086            
2087            results = ServiceUtil.returnSuccess();
2088            results.put("glReconciliationId", glReconciliationId);
2089            return results;
2090
2091        } catch (GenericEntityException ex) {
2092           return ServiceUtil.returnError(ex.getMessage());
2093        } catch (GenericServiceException ex) {
2094           return ServiceUtil.returnError(ex.getMessage());
2095        }
2096    }
2097    
2098    /**
2099     * Helps closing all time periods by closing out all time periods of an asOfDate and a periodTypeId for an organizationPartyId
2100     * @param delegator
2101     * @param dispatcher
2102     * @param organizationPartyId
2103     * @param periodTypeId
2104     * @param asOfDate
2105     * @param userLogin
2106     * @throws GenericEntityException
2107     * @throws GenericServiceException
2108     */

2109    private static void closeAllTimePeriodsByType(GenericDelegator delegator, LocalDispatcher dispatcher, String JavaDoc organizationPartyId,
2110            String JavaDoc periodTypeId, Date asOfDate, GenericValue userLogin) throws GenericEntityException, GenericServiceException {
2111        try {
2112            List timePeriods = delegator.findByAnd("CustomTimePeriod", UtilMisc.toMap("organizationPartyId", organizationPartyId, "periodTypeId", periodTypeId,
2113                    "thruDate", asOfDate, "isClosed", "N"));
2114            if ((timePeriods != null) && (timePeriods.size() > 0)) {
2115                for (Iterator tPi = timePeriods.iterator(); tPi.hasNext(); ) {
2116                    GenericValue timePeriod = (GenericValue) tPi.next();
2117                    Debug.logInfo("Now trying to close time period " + timePeriod, module);
2118                    Map tmpResult = dispatcher.runSync("closeTimePeriod", UtilMisc.toMap("organizationPartyId", organizationPartyId,
2119                            "customTimePeriodId", timePeriod.getString("customTimePeriodId"), "userLogin", userLogin));
2120                    if (tmpResult == null) {
2121                        throw new GenericServiceException("Failed to close time period for " + organizationPartyId + " and time period " + timePeriod.getString("customTimePeriodId"));
2122                    } else if (!tmpResult.get(ModelService.RESPONSE_MESSAGE).equals(ModelService.RESPOND_SUCCESS)) {
2123                        throw new GenericServiceException("Failed to close time period for " + organizationPartyId + " and time period " + timePeriod.getString("customTimePeriodId")
2124                                + ": " + tmpResult.get(ModelService.ERROR_MESSAGE));
2125                    }
2126                }
2127            }
2128        } catch (GenericEntityException ex) {
2129            Debug.logError(ex.getMessage(), module);
2130            throw new GenericEntityException(ex);
2131        } catch (GenericServiceException ex) {
2132            Debug.logError(ex.getMessage(), module);
2133            throw new GenericServiceException(ex);
2134        }
2135    }
2136    
2137    /**
2138     * Service to post foreign exchange gain/loss to GL.
2139     * if invoice amount > payment amount
2140     * if invoice type is sales invoice then Debit Foreign exchange loss, Credit Account receivable
2141     * if invoice type is purchace invoice then Debit Account payable, Credit Foreign exchange gain
2142     * if invoice amount < payment amount
2143     * if invoice type is sales invoice then Debit Account receivable, Credit Foreign exchange gain
2144     * if invoice type is purchace invoice then Debit Foreign exchange loss, Credit Account payable
2145     * @author <a HREF="mailto:david.zelaya.h@gmail.com">David Zelaya</a>
2146     */

2147    public static Map postForeignExchangeMatchingToGl(DispatchContext dctx, Map context) {
2148        GenericDelegator delegator = dctx.getDelegator();
2149        LocalDispatcher dispatcher = dctx.getDispatcher();
2150        GenericValue userLogin = (GenericValue) context.get("userLogin");
2151
2152        String JavaDoc invoiceId = (String JavaDoc) context.get("invoiceId");
2153        try {
2154            // get the invoice and make sure it has an invoiceTypeId
2155
GenericValue invoice = delegator.findByPrimaryKeyCache("Invoice", UtilMisc.toMap("invoiceId", invoiceId));
2156            if (invoice == null) {
2157                return ServiceUtil.returnError("No invoice found for invoiceId of " + invoiceId);
2158            } else if (invoice.get("invoiceTypeId") == null) {
2159                return ServiceUtil.returnError("Invoice " + invoiceId + " has a null invoice type and cannot be processed");
2160            }
2161            String JavaDoc invoiceTypeId = (String JavaDoc) invoice.get("invoiceTypeId");
2162            
2163            // accounting data
2164
String JavaDoc transactionPartyId = null;
2165            String JavaDoc transactionPartyRoleTypeId = null;
2166            String JavaDoc organizationPartyId = null;
2167            String JavaDoc offsettingGlAccountTypeId = null;
2168            Map result = null;
2169            
2170            // set accounting data according to invoiceTypeId
2171
if ("SALES_INVOICE".equals(invoiceTypeId)) {
2172                offsettingGlAccountTypeId = "ACCOUNTS_RECEIVABLE";
2173                organizationPartyId = invoice.getString("partyIdFrom");
2174                transactionPartyId = invoice.getString("partyId");
2175                transactionPartyRoleTypeId = "BILL_TO_CUSTOMER";
2176            } else if ("PURCHASE_INVOICE".equals(invoiceTypeId)) {
2177                offsettingGlAccountTypeId = "ACCOUNTS_PAYABLE";
2178                organizationPartyId = invoice.getString("partyId");
2179                transactionPartyId = invoice.getString("partyIdFrom");
2180                transactionPartyRoleTypeId = "BILL_FROM_VENDOR";
2181            } else if ("CUST_RTN_INVOICE".equals(invoiceTypeId)) {
2182                result = ServiceUtil.returnSuccess();
2183                result.put("acctgTransId", null);
2184                return result;
2185            } else {
2186                return ServiceUtil.returnError("Unknown invoiceTypeId '" + invoiceTypeId + "' for Invoice [" + invoiceId + "]");
2187            }
2188            // get the invoice currency
2189
String JavaDoc invoiceCurrencyId = invoice.getString("currencyUomId");
2190            // get the company currency
2191
GenericValue partyAcctgPreference = delegator.findByPrimaryKey("PartyAcctgPreference", UtilMisc.toMap("partyId", organizationPartyId));
2192            String JavaDoc companyCurrencyId = partyAcctgPreference.getString("baseCurrencyUomId");
2193            
2194            // get the invoice transaction amount
2195
BigDecimal JavaDoc conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, organizationPartyId, invoice.getString("currencyUomId"), invoice.getTimestamp("invoiceDate")));
2196            BigDecimal JavaDoc invoiceAmount = InvoiceWorker.getInvoiceTotalBd(invoice).multiply(conversionFactor).setScale(decimals, rounding);
2197            
2198            // get the invoice payment applications transaction amount
2199
BigDecimal JavaDoc paymentAmount = ZERO;
2200            List paymentApplications = delegator.findByAnd("PaymentApplication", UtilMisc.toMap("invoiceId", invoiceId));
2201            Iterator paymentApplicationIt = paymentApplications.iterator();
2202            while (paymentApplicationIt.hasNext()) {
2203                GenericValue paymentApplication = (GenericValue)paymentApplicationIt.next();
2204                GenericValue payment = paymentApplication.getRelatedOne("Payment");
2205                conversionFactor = new BigDecimal JavaDoc(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, organizationPartyId, payment.getString("currencyUomId"), payment.getTimestamp("effectiveDate")));
2206                BigDecimal JavaDoc amount = paymentApplication.getBigDecimal("amountApplied");
2207                paymentAmount = paymentAmount.add(amount.multiply(conversionFactor)).setScale(decimals, rounding);
2208            }
2209            
2210            // accounting data
2211
String JavaDoc fxGainLossGlAccountTypeId = null;
2212            String JavaDoc fxGLCreditDebitFlag = null;
2213            String JavaDoc transCreditDebitFlag = null;
2214
2215            // get the transaction amount and the foreign exchange gain/loss account type
2216
BigDecimal JavaDoc transactionAmount = invoiceAmount.subtract(paymentAmount);
2217            if (!invoiceCurrencyId.equals(companyCurrencyId) && (invoiceAmount.compareTo(paymentAmount) != 0)) {
2218                if (invoiceAmount.compareTo(paymentAmount) > 0 ){
2219                    if ("SALES_INVOICE".equals(invoiceTypeId)) { // sales invoice
2220
fxGainLossGlAccountTypeId = "FX_LOSS_ACCOUNT";
2221                        fxGLCreditDebitFlag = "D";
2222                        transCreditDebitFlag = "C";
2223                    } else { // purchase invoice
2224
fxGainLossGlAccountTypeId = "FX_GAIN_ACCOUNT";
2225                        fxGLCreditDebitFlag = "C";
2226                        transCreditDebitFlag = "D";
2227                    }
2228                } else {
2229                       transactionAmount = transactionAmount.negate();
2230                    if ("SALES_INVOICE".equals(invoiceTypeId)) { // sales invoice
2231
fxGainLossGlAccountTypeId = "FX_GAIN_ACCOUNT";
2232                           fxGLCreditDebitFlag = "C";
2233                           transCreditDebitFlag = "D";
2234                    } else { // purchase invoice
2235
fxGainLossGlAccountTypeId = "FX_LOSS_ACCOUNT";
2236                        fxGLCreditDebitFlag = "D";
2237                        transCreditDebitFlag = "C";
2238                    }
2239                }
2240            }
2241            else {
2242                result = ServiceUtil.returnSuccess();
2243                result.put("acctgTransId", null);
2244                return result;
2245            }
2246            
2247            // get the foreign exchange gain/loss account
2248
String JavaDoc fxGlAccountId = UtilAccounting.getDefaultAccountId(fxGainLossGlAccountTypeId, organizationPartyId, delegator);
2249            // get the invoice transaction account
2250
String JavaDoc transGlAccountId = UtilAccounting.getDefaultAccountId(offsettingGlAccountTypeId, organizationPartyId, delegator);
2251            
2252            // Transaction to credit/debit the foreign exchange gain/loss account
2253
Map input = UtilMisc.toMap("glAccountId", fxGlAccountId, "organizationPartyId", organizationPartyId, "partyId", transactionPartyId);
2254            input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
2255            input.put("acctgTransEntryTypeId", "_NA_");
2256            input.put("debitCreditFlag", fxGLCreditDebitFlag);
2257            input.put("acctgTransEntrySeqId", Integer.toString(0));
2258            input.put("roleTypeId", transactionPartyRoleTypeId);
2259            GenericValue creditAcctTrans = delegator.makeValue("AcctgTransEntry", input);
2260
2261            // Transaction to debit/credit the invoice transaction account
2262
input = UtilMisc.toMap("glAccountId", transGlAccountId, "organizationPartyId", organizationPartyId, "partyId", transactionPartyId);
2263            input.put("amount", new Double JavaDoc(transactionAmount.doubleValue()));
2264            input.put("acctgTransEntryTypeId", "_NA_");
2265            input.put("debitCreditFlag", transCreditDebitFlag);
2266            input.put("acctgTransEntrySeqId", Integer.toString(1));
2267            input.put("roleTypeId", transactionPartyRoleTypeId);
2268            GenericValue debitAcctTrans = delegator.makeValue("AcctgTransEntry", input);
2269
2270            // Perform the transaction
2271
input = UtilMisc.toMap("transactionDate", UtilDateTime.nowTimestamp(), "partyId", transactionPartyId, "userLogin", userLogin);
2272            input.put("acctgTransEntries", UtilMisc.toList(creditAcctTrans, debitAcctTrans));
2273            input.put("invoiceId", invoiceId);
2274            input.put("roleTypeId", transactionPartyRoleTypeId);
2275            input.put("glFiscalTypeId", "ACTUAL");
2276            input.put("acctgTransTypeId", "FX_GAINLOSS_ACCTG");
2277            Map servResult = dispatcher.runSync("createAcctgTransAndEntries", input);
2278
2279            if (((String JavaDoc) servResult.get(ModelService.RESPONSE_MESSAGE)).equals(ModelService.RESPOND_SUCCESS)) {
2280                result = ServiceUtil.returnSuccess();
2281                result.put("acctgTransId", servResult.get("acctgTransId"));
2282                return(result);
2283            } else {
2284                return servResult;
2285            }
2286        } catch (GenericEntityException ex) {
2287            return(ServiceUtil.returnError(ex.getMessage()));
2288        } catch (GenericServiceException ex) {
2289            return(ServiceUtil.returnError(ex.getMessage()));
2290        }
2291    }
2292    
2293}
2294
Popular Tags