From 2ad8d5e02d79f47f8b7d57ecfdf953334ca25f09 Mon Sep 17 00:00:00 2001 From: z5k1 Date: Thu, 14 Dec 2017 09:37:54 +0100 Subject: [PATCH] IDEMPIERE-3522 Fix amount in words for zh_CN --- .../src/org/compiere/util/AmtInWords_ZH.java | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 org.adempiere.base/src/org/compiere/util/AmtInWords_ZH.java diff --git a/org.adempiere.base/src/org/compiere/util/AmtInWords_ZH.java b/org.adempiere.base/src/org/compiere/util/AmtInWords_ZH.java new file mode 100644 index 0000000000..d273dd977a --- /dev/null +++ b/org.adempiere.base/src/org/compiere/util/AmtInWords_ZH.java @@ -0,0 +1,298 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + * For the text or an alternative of this public license, you may reach us * + * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA * + * or via info@compiere.org or http://www.compiere.org/license.html * + *****************************************************************************/ +package org.compiere.util; +import java.math.BigDecimal; + +/** + * Amount in Words for Zh_CN (CNY in Simplified Chinese) + * @author z5k1@qq.com + * @version $Id: AmtInWords_ZH.java,v1.0 2017/09/21 00:54:35 $ + * + * The class is based on the AmtInWords_EN.java written by jjanke + * The algorithm is based on code named snhongtenzone@foxmail.com + * + * Rule: + * 1. CNY is an currency of 10000-unit scale, dislike dollar with 1000-unit scale. + * 2. CNY currency accuracy is 0.01. And it's under a strict decimal number system. + * 3. Option of add an "zero" after "Yuan" is false. (Line 186) + * Reference: [1997]393 People's Bank of China "the Measures for Payment and Settlement" + * \u53c2\u8003: \u4e2d\u56fd\u4eba\u6c11\u94f6\u884c \u94f6\u53d1[1997]393\u53f7 \u300a\u652f\u4ed8\u7ed3\u7b97\u529e\u6cd5\u300b\u9644\u4ef6\u300a\u6b63\u786e\u586b\u5199\u7968\u636e\u548c\u7ed3\u7b97\u51ed\u8bc1\u7684\u57fa\u672c\u89c4\u5b9a\u300b + * + * + * Results: (Rule 3 = true) + * + * 0.00 \u96f6\u5143\u6574 \uff08\u6d4b\u8bd5\uff1a\u91d1\u989d\u4e3a 0\uff09 + * 0.03 \u53c1\u5206 \uff08\u6d4b\u8bd5\uff1a\u89d20\u5206\u975e0\uff0c\u5916\u52a0 \u51430\uff09 + * 0.234 \u8d30\u89d2\u53c1\u5206 \uff08\u6d4b\u8bd5\uff1a\u89d2\u975e0\u5206\u975e0\uff0c\u820d4\uff09 + * 1.001 \u58f9\u5143\u6574 \uff08\u6d4b\u8bd5\uff1a\u89d20\u52060\uff0c\u52a0 \u201c \u6574 \u201d\uff0c\u820d1\uff09 + * 1.035 \u58f9\u5143\u96f6\u8086\u5206 \uff08\u89c4\u52194.4\uff1a\u89d20\u5206\u975e0\uff0c\u52a0\u96f6\uff0c\u51655\uff09 + * 1.205 \u58f9\u5143\u8d30\u89d2\u58f9\u5206 \uff08\u6d4b\u8bd5\uff1a\u89d2\u975e0\u52060\uff0c\u51655\u540e\uff0c\u89d2\u975e0\u5206\u975e0\uff09 + * 1.3345 \u58f9\u5143\u53c1\u89d2\u53c1\u5206 \uff08\u6d4b\u8bd5\uff1a\u5c0f\u6570\u70b94\u4f4d\uff09 + * 10.03 \u58f9\u62fe\u5143\u96f6\u53c1\u5206 \uff08\u89c4\u52194.4\uff1a\u89d20\u5206\u975e0\uff0c\u52a0\u96f6\uff09 + * 10.23 \u58f9\u62fe\u5143\u96f6\u8d30\u89d2\u53c1\u5206 \uff08\u89c4\u52194.3\uff1a\u51430\u89d2\u975e0\uff0c\u53ef\u9009\u52a0\u96f6\uff09 + * 23.00 \u8d30\u62fe\u53c1\u5143\u6574 \uff08\u89c4\u52192\uff1a\u52a0 \u201c \u6574 \u201d\uff09 + * 100.20 \u58f9\u4f70\u5143\u96f6\u8d30\u89d2 \uff08\u89c4\u52194.2 \u591a\u96f6\u5199\u4e00\u96f6\uff0c\u89d2\u975e0\u52060\uff09 + * 103.45 \u58f9\u4f70\u96f6\u53c1\u5143\u8086\u89d2\u4f0d\u5206 \uff08\u89c4\u52194.1\uff1a\u67090\u52a0 \u201c \u96f6 \u201d\uff0c\u89d2\u975e0\u5206\u975e0\uff09 + * 2,345 \u8d30\u4edf\u53c1\u4f70\u8086\u62fe\u4f0d\u5143\u6574 \uff08\u89c4\u52192\uff1a\u52a0 \u201c \u6574 \u201d\uff09 + * 1020.45 \u58f9\u4edf\u96f6\u8d30\u62fe\u5143\u96f6\u8086\u89d2\u4f0d\u5206 \uff08\u89c4\u52194.1+4.3\uff1a\u67090\u52a0 \u201c \u96f6 \u201d\uff1b\u51430\u89d2\u975e0\uff0c\u53ef\u9009\u52a0\u96f6\uff09 + * 1,234.56 \u58f9\u4edf\u8d30\u4f70\u53c1\u62fe\u8086\u5143\u4f0d\u89d2\u9646\u5206 \uff08\u6d4b\u8bd5\uff1a\u5343\u5206\u4f4d \u662f\u5426\u6b63\u786e\uff09 + * 12345.78 \u58f9\u4e07\u8d30\u4edf\u53c1\u4f70\u8086\u62fe\u4f0d\u5143\u67d2\u89d2\u634c\u5206 \uff08\u6d4b\u8bd5\uff1a\u5343\u5206\u4f4d \u662f\u5426\u6b63\u786e\uff09 + * 100457.89 \u58f9\u62fe\u4e07\u96f6\u8086\u4f70\u4f0d\u62fe\u67d2\u5143\u634c\u89d2\u7396\u5206 \uff08\u89c4\u52194.2\uff1a\u591a\u96f6\u5199\u4e00\u96f6\uff09 + * 103457.89 \u58f9\u62fe\u4e07\u96f6\u53c1\u4edf\u8086\u4f70\u4f0d\u62fe\u67d2\u5143\u634c\u89d2\u7396\u5206 \uff08\u89c4\u52194.3\uff1a\u4e070\u5343\u975e\u96f6\uff0c\u53ef\u9009\u52a0\u96f6\uff09 + * 107000.03 \u58f9\u62fe\u4e07\u96f6\u67d2\u4edf\u5143\u96f6\u53c1\u5206 \uff08\u89c4\u52194.3+4.4\uff1a\u4e070\u51430\u5343\u975e0\uff0c\u4e2d\u95f4\u591a0\uff0c\u89d20\uff0c\u53ef\u9009\u52a0\u96f6\uff09 + * 9,000,000.00 \u7396\u4f70\u4e07\u5143\u6574 \uff08\u89c4\u5219: 4\u4f4d\u8fdb\u5236\uff09 + * 10,000,000.00 \u58f9\u4edf\u4e07\u5143\u6574 \uff08\u6d4b\u8bd5\uff1a4\u4f4d\u8fdb\u5236\uff09 + * 10220134 \u58f9\u4edf\u96f6\u8d30\u62fe\u8d30\u4e07\u96f6\u58f9\u4f70\u53c1\u62fe\u8086\u5143\u6574 \uff08\u6d4b\u8bd5\uff1a\u65e0\u5343\u5206\u4f4d \u201c\u662f\u5426\u6b63\u786e\uff09 + * 100,234,578.90 \u58f9\u4ebf\u96f6\u8d30\u62fe\u53c1\u4e07\u8086\u4edf\u4f0d\u4f70\u67d2\u62fe\u634c\u5143\u7396\u89d2 \uff08\u6d4b\u8bd5\uff1a\u4ebf\uff09 + * 203010000.56 \u8d30\u4ebf\u96f6\u53c1\u4f70\u96f6\u58f9\u4e07\u5143\u96f6\u4f0d\u89d2\u9646\u5206 \uff08\u6d4b\u8bd5\uff1a\u4ebf\u96f6\uff0c\u4e07\u96f6\uff0c\u5143\uff09 + * 103,004,000.90 \u58f9\u4ebf\u96f6\u53c1\u4f70\u4e07\u96f6\u8086\u4edf\u5143\u96f6\u7396\u89d2 \uff08\u89c4\u52194.2+4.3\uff1a\u591a\u96f6\u5199\u4e00\u96f6\uff0c\u4e070\u5343\u975e\u96f6\uff0c\u4e2d\u95f4\u591a0\uff0c\u53ef\u9009\u52a0\u96f6\uff09 + * 1,201,034,578.90 \u58f9\u62fe\u8d30\u4ebf\u96f6\u58f9\u4f70\u96f6\u53c1\u4e07\u8086\u4edf\u4f0d\u4f70\u67d2\u62fe\u634c\u5143\u7396\u89d2 \uff08\u6d4b\u8bd5\uff1a\u5341\u4ebf\uff0c3\u4e2a\u5343\u5206\u4f4d \u662f\u5426\u6b63\u786e\uff09 + * 90,000,000,000 \u7396\u4f70\u4ebf\u5143\u6574 \uff08\u6d4b\u8bd5\uff1a4\u4f4d\u8fdb\u5236\uff09 + * 100,000,000,000 \u58f9\u4edf\u4ebf\u5143\u6574 \uff08\u6d4b\u8bd5\uff1a4\u4f4d\u8fdb\u5236\uff09 + * 1,932,110,345,780 \u58f9\u5146\u7396\u4edf\u53c1\u4f70\u8d30\u62fe\u58f9\u4ebf\u58f9\u4edf\u96f6\u53c1\u62fe\u8086\u4e07\u4f0d\u4edf\u67d2\u4f70\u634c\u62fe\u5143\u6574 \uff08\u6d4b\u8bd5\uff1a13\u4f4d\u6574\u6570\uff0c\u5146\uff0c4\u4e2a\u5343\u5206\u4f4d\uff09 + * 10,000,000,000,000 \u9519\u8bef \uff08\u6d4b\u8bd5\uff1a\u8d85\u8fc7\u6216\u7b49\u4e8e\u6700\u5927\u4f4d\u657010\u5146\uff0c\u6570\u5b57\u5fc5\u987b\u5c0f\u4e8e14\u4f4d\uff09 + * -1220.354 \u8d1f\u58f9\u4edf\u8d30\u4f70\u8d30\u62fe\u5143\u96f6\u53c1\u89d2\u4f0d\u5206 \uff08\u6d4b\u8bd5\uff1a\u8d1f\u6570\uff0c\u56db\u820d\u4e94\u5165\uff09 + * 1.222,33 \u9519\u8bef \uff08\u6d4b\u8bd5\uff1a\u975e\u6cd5\u5b57\u7b26\uff09 + * + */ + +public class AmtInWords_ZH implements AmtInWords +{ + /** + * AmtInWords_ZH + */ + public AmtInWords_ZH () + { + super (); + } // AmtInWords_ZH + + /** Currency scales */ + private static final String[] scaleNames = + { + "\u5206", // 0 0.01 + "\u89d2", // 1 0.1 + "\u5143", // 2 1 \uff0810^0\uff09 + "\u62fe", // 3 10 + "\u4f70", // 4 100 + "\u4edf", // 5 1000 + "\u4e07", // 6 10000 \uff0810^4\uff09 + "\u62fe", // 7 10*10000 + "\u4f70", // 8 100*10000 + "\u4edf", // 9 1000*10000 + "\u4ebf", // 10 100000000 \uff0810^8\uff09 \u4e07\u4e07\u4e3a\u4ebf + "\u62fe", // 11 10*100000000 + "\u4f70", // 12 100*100000000 + "\u4edf", // 13 1000*100000000 + "\u5146", // 14 1000000000000 \uff0810^12\uff09 \u4e07\u4ebf\u4e3a\u5146 + + }; + /** numbers 0 to 9 */ + private static final String[] numNames = + { + "\u96f6", // 0 + "\u58f9", // 1 + "\u8d30", // 2 + "\u53c1", // 3 + "\u8086", // 4 + "\u4f0d", // 5 + "\u9646", // 6 + "\u67d2", // 7 + "\u634c", // 8 + "\u7396" // 9 + }; + + private static final String CNY_NEGATIVE = "\u8d1f"; + private static final String CNY_FULL = "\u6574"; + private static final String CNY_ZEOR_FULL = "\u96f6\u5143\u6574"; + + /** + * Convert + * @description Convert BigDecimal number to String + * @param number (-5432.135 with no period) + * @return amt (amt in words of CNY) + */ + private String convert (BigDecimal number) + { + StringBuffer sb = new StringBuffer(); + int signum = number.signum(); // positive , 0 , negative + int numUnit = 0; // each number character + int numIndex = 0; // each number position + int zeroSize = 0; // sizes of zero + + // number = 0 + if (signum == 0){ + return CNY_ZEOR_FULL; // zero_full + } + + // round to 1/100, get long number ,eg -5432.135 = 543214 + long lnumber = number.movePointRight(2).setScale(0, BigDecimal.ROUND_HALF_UP).abs().longValue(); + + // number must less 10000000000000 (10^13) + if (lnumber >= 1000000000000000L) { + return "\u4eb2\uff0c\u91d1\u989d\u8f6c\u6362\u4e0a\u9650\u4e3a10\u5146\uff0810\u4e07\u4ebf\uff09"; + } + + // get the last 2 number (decimal number) ,eg 543214 = 14 + long decamt = lnumber % 100; + boolean getZero = false; // sign of zero + + // decimal options\uff1a00 = 0, 01 = 1, 10, 11 + + // decimal = 00 + if (!(decamt > 0)) { // special decimal case 00 + numIndex = 2; // 2, begin from "Yuan" + lnumber = lnumber / 100; // eg: 5432.00 = 543200 = 5432 + getZero = true; + } + + // decimal = 10 + if ((decamt > 0) && (!(decamt % 10 > 0))) { // special decimal case 10 + numIndex = 1; // 1, begin from "Jiao" + lnumber = lnumber / 10; // eg: 5432.10 = 543210 = 54321 + getZero = true; + } + + // begin foreach for all cases + while (true) { + if (lnumber <= 0) { // each character done + break; + } + // get the last number each + numUnit = (int) (lnumber % 10); + if (numUnit > 0) { // Character = 1-9 ,insert (number + scale) + if ((numIndex == 9) && (zeroSize >= 3)) { // special 9 (8th), eg: 10,000,000 + sb.insert(0, scaleNames[6]); // add "WAN" + } + if ((numIndex == 13) && (zeroSize >= 3)) { // special 13 (11st), eg: 100,000,000,000 + sb.insert(0, scaleNames[10]); // add "YI" + } + sb.insert(0, scaleNames[numIndex]); // first insert scale to Chinese + sb.insert(0, numNames[numUnit]); // then insert number to Chinese + getZero = false; + zeroSize = 0; + } else { // Character = 0, insert (scale + 0 ) + ++zeroSize; + if (!(getZero) & numIndex != 2) { // separated zero and option of "YUAN" suffix + sb.insert(0, numNames[numUnit]); // first insert 0 to Chinese + } // + if (numIndex == 2) { // option1: "YUAN"=0 + sb.insert(0, scaleNames[numIndex]); // then insert scale to Chinese + } // option2: "WAN"|"YI"|"ZHAO" =0 + else if (((numIndex - 2) % 4 == 0) && (lnumber % 1000 > 0)) { // special 4th,8th, eg: 9,000,000 or 90,000,000,000 + sb.insert(0, scaleNames[numIndex]); // then insert scale to Chinese + } + getZero = true; // multi-zero nothing added + } + + lnumber = lnumber / 10; // cut the last number off each + ++numIndex; // ++ position of number + } + + // add suffix of "zheng" when decimal = 00 + if (!(decamt > 0)) { + sb.append(CNY_FULL); // add suffix CNY_full + } + + // add prefix of "fu" when negative + if (signum == -1) { + sb.insert(0, CNY_NEGATIVE); // add prefix negative + } + + return sb.toString(); // return amt string completed + } // convert + + /************************************************************************** + * Get Amount in Words + * @param amount numeric amount (352.80) + * @return amount in words + * @throws Exception + */ + public String getAmtInWords (String amount) throws Exception + { + if (amount == null || amount.length() <= 0) // null string + return amount; + + //TODO: Check string illegal character + + amount = amount.replaceAll (",", ""); // fire period "," + //TODO: 1.234,56 as comma replace period + + StringBuilder sb = new StringBuilder (); + BigDecimal cny = new BigDecimal(amount); // convert String to BigDecimal + sb.append (convert (cny)); // get result string from convert + return sb.toString (); // return + } + + /** + * Test Print + * @param amt amount + */ + private void print (String amt) + { + try + { + System.out.println(amt + " = " + getAmtInWords(amt)); + } + catch (Exception e) + { + e.printStackTrace(); + } + } // print + + /** + * Test + * @param args ignored + */ + public static void main (String[] args) + { + AmtInWords_ZH aiw = new AmtInWords_ZH(); + aiw.print (null); + aiw.print (""); + aiw.print ("0.00"); + aiw.print ("0.03"); + aiw.print ("0.234"); + aiw.print ("1.001"); + aiw.print ("1.035"); + aiw.print ("1.205"); + aiw.print ("1.3345"); + aiw.print ("10.03"); + aiw.print ("10.23"); + aiw.print ("23.00"); + aiw.print ("100.20"); + aiw.print ("103.45"); + aiw.print ("2,345"); + aiw.print ("1020.45"); + aiw.print ("1,234.56"); + aiw.print ("12345.78"); + aiw.print ("100457.89"); + aiw.print ("103457.89"); + aiw.print ("107000.03"); + aiw.print ("9,000,000.00"); // Line 189 + aiw.print ("10,000,000.00"); // Line 171 + aiw.print ("10220134"); + aiw.print ("100,234,578.90"); + aiw.print ("203010000.56"); + aiw.print ("103,004,000.90"); + aiw.print ("1,201,034,578.90"); + aiw.print ("90,000,000,000"); // Line 189 + aiw.print ("100,000,000,000"); // Line 174 + aiw.print ("1,932,110,345,780"); + aiw.print ("10,000,000,000,000"); + aiw.print ("-1220.354"); +// aiw.print ("1.234,56"); ERROR + } // main +}