Repository: dream-num/univer (TypeScript)

Issue:
[Bug] 
### Before you submit this issue, have you checked the following

- [X] Is this really a problem?
- [X] I have searched the Github Issues for similar issues, but did not find anything.

### Affected packages and versions

0.5.1

### Reproduction link

Using "=PRODUCT" with empty cells results in 1. E.g. having E11 "=PRODUCT(C11,D11)" with C11 and D11 empty will put a 1 in E11.  

### Expected behavior

Cell should have 0, just like when using * multiplication. In MS Excel it works like that. 

### Actual behavior

Cell content is 1

### System information

_No response_


Discussion:

Patch:
diff --git a/packages/engine-formula/src/functions/math/product/index.ts b/packages/engine-formula/src/functions/math/product/index.ts
index ec55412dd176..3194950cba3f 100644
--- a/packages/engine-formula/src/functions/math/product/index.ts
+++ b/packages/engine-formula/src/functions/math/product/index.ts
@@ -26,53 +26,66 @@ export class Product extends BaseFunction {
 
     override calculate(...variants: BaseValueObject[]) {
         let accumulatorAll: BaseValueObject = NumberValueObject.create(1);
+        let noCalculate = true;
+
         for (let i = 0; i < variants.length; i++) {
             let variant = variants[i];
 
-            if (variant.isNull()) {
-                continue;
-            }
-
-            if (variant.isString()) {
-                variant = variant.convertToNumberObjectValue();
+            if (variant.isError()) {
+                return variant;
             }
 
             if (variant.isArray()) {
-                variant = this._multiplyArray(variant as ArrayValueObject);
-            }
+                let isError = false;
+                let errorObject: ErrorValueObject | null = null;
 
-            if (variant.isError()) {
-                return variant as ErrorValueObject;
-            }
+                (variant as ArrayValueObject).iterator((valueObject) => {
+                    if (valueObject?.isError()) {
+                        isError = true;
+                        errorObject = valueObject as ErrorValueObject;
+                        return false;
+                    }
 
-            accumulatorAll = accumulatorAll.multiply(variant);
+                    // cell range ignore string, boolean, blank cell
+                    if (!valueObject || valueObject.isNull() || valueObject.isString() || valueObject.isBoolean()) {
+                        return true;
+                    }
 
-            if (accumulatorAll.isError()) {
-                return accumulatorAll;
-            }
-        }
+                    accumulatorAll = accumulatorAll.multiply(valueObject);
+                    noCalculate = false;
+                });
 
-        return accumulatorAll;
-    }
+                if (isError) {
+                    return errorObject!;
+                }
+            } else {
+                if (variant.isNull()) {
+                    continue;
+                }
+
+                // not cell reference, number string and boolean can be calculated
+                if (variant.isString()) {
+                    variant = variant.convertToNumberObjectValue();
+                }
+
+                if (variant.isError()) {
+                    return variant;
+                }
 
-    private _multiplyArray(array: ArrayValueObject) {
-        let result: BaseValueObject = NumberValueObject.create(1);
-        array.iterator((valueObject) => {
-            // 'test', ' ',  blank cell, TRUE and FALSE are ignored
-            if (valueObject == null || valueObject.isString() || valueObject.isBoolean() || valueObject.isNull()) {
-                return true; // continue
+                accumulatorAll = accumulatorAll.multiply(variant);
+                noCalculate = false;
             }
 
-            if (valueObject.isError()) {
-                result = valueObject;
-                return false; // break
+            if (accumulatorAll.isError()) {
+                return accumulatorAll;
             }
+        }
 
-            result = (result as NumberValueObject).multiply(
-                valueObject as BaseValueObject
-            ) as BaseValueObject;
-        });
+        // if all params is ignored, return 0
+        if (noCalculate) {
+            return NumberValueObject.create(0);
+        }
 
-        return result;
+        return accumulatorAll;
     }
 }


Test patch:
diff --git a/packages/engine-formula/src/functions/__tests__/nested-functions.spec.ts b/packages/engine-formula/src/functions/__tests__/nested-functions.spec.ts
index cea60663532a..a7bcbb453f95 100644
--- a/packages/engine-formula/src/functions/__tests__/nested-functions.spec.ts
+++ b/packages/engine-formula/src/functions/__tests__/nested-functions.spec.ts
@@ -40,6 +40,7 @@ import { FUNCTION_NAMES_LOOKUP } from '../lookup/function-names';
 import { Xlookup } from '../lookup/xlookup';
 import { Xmatch } from '../lookup/xmatch';
 import { FUNCTION_NAMES_MATH } from '../math/function-names';
+import { Product } from '../math/product';
 import { Sum } from '../math/sum';
 import { Sumif } from '../math/sumif';
 import { Sumifs } from '../math/sumifs';
@@ -343,8 +344,8 @@ describe('Test nested functions', () => {
             new Sum(FUNCTION_NAMES_MATH.SUM),
             new Choose(FUNCTION_NAMES_LOOKUP.CHOOSE),
             new Len(FUNCTION_NAMES_TEXT.LEN),
-            new Divided(FUNCTION_NAMES_META.DIVIDED)
-
+            new Divided(FUNCTION_NAMES_META.DIVIDED),
+            new Product(FUNCTION_NAMES_MATH.PRODUCT)
         );
 
         calculate = (formula: string) => {
@@ -449,5 +450,13 @@ describe('Test nested functions', () => {
             result = calculate('=SUMIF(A11:C11,A12,A11:C11)');
             expect(result).toBe(2);
         });
+
+        it('Product test, cell number string ignore, param number string can be calculated', () => {
+            let result = calculate('=PRODUCT(A12)');
+            expect(result).toBe(0);
+
+            result = calculate('=PRODUCT("2")');
+            expect(result).toBe(2);
+        });
     });
 });
diff --git a/packages/engine-formula/src/functions/math/product/__tests__/index.spec.ts b/packages/engine-formula/src/functions/math/product/__tests__/index.spec.ts
index ad50e2923a3c..b56d0683d805 100644
--- a/packages/engine-formula/src/functions/math/product/__tests__/index.spec.ts
+++ b/packages/engine-formula/src/functions/math/product/__tests__/index.spec.ts
@@ -16,12 +16,13 @@
 
 import { describe, expect, it } from 'vitest';
 
-import { FUNCTION_NAMES_MATH } from '../../function-names';
-import { BooleanValueObject, NullValueObject, NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object';
-import { ArrayValueObject, transformToValueObject } from '../../../../engine/value-object/array-value-object';
 import { ErrorType } from '../../../../basics/error-type';
-import { Product } from '../index';
+import { ArrayValueObject, transformToValueObject } from '../../../../engine/value-object/array-value-object';
 import { ErrorValueObject } from '../../../../engine/value-object/base-value-object';
+import { BooleanValueObject, NullValueObject, NumberValueObject, StringValueObject } from '../../../../engine/value-object/primitive-object';
+import { getObjectValue } from '../../../__tests__/create-function-test-bed';
+import { FUNCTION_NAMES_MATH } from '../../function-names';
+import { Product } from '../index';
 
 describe('Test product function', () => {
     const testFunction = new Product(FUNCTION_NAMES_MATH.PRODUCT);
@@ -31,48 +32,48 @@ describe('Test product function', () => {
             const var1 = NumberValueObject.create(1);
             const var2 = NumberValueObject.create(2);
             const result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(2);
+            expect(getObjectValue(result)).toBe(2);
         });
         it('Var1 is string number, var2 is string number', () => {
             const var1 = new StringValueObject('1');
             const var2 = new StringValueObject('2');
             const result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(2);
+            expect(getObjectValue(result)).toBe(2);
         });
         it('Var1 is number, var2 is string', () => {
             const var1 = NumberValueObject.create(1);
             const var2 = StringValueObject.create('test');
             const result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(ErrorType.VALUE);
+            expect(getObjectValue(result)).toBe(ErrorType.VALUE);
         });
         it('Var1 is number, var2 is string number', () => {
             const var1 = NumberValueObject.create(1);
             const var2 = StringValueObject.create('2');
             const result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(2);
+            expect(getObjectValue(result)).toBe(2);
         });
         it('Var1 is number, var2 is boolean', () => {
             const var1 = NumberValueObject.create(2);
 
             let var2 = BooleanValueObject.create(true);
             let result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(2);
+            expect(getObjectValue(result)).toBe(2);
 
             var2 = BooleanValueObject.create(false);
             result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(0);
+            expect(getObjectValue(result)).toBe(0);
         });
         it('Var1 is number, var2 is null', () => {
             const var1 = NumberValueObject.create(1);
             const var2 = NullValueObject.create();
             const result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(1);
+            expect(getObjectValue(result)).toBe(1);
         });
         it('Var1 is number, var2 is error', () => {
             const var1 = NumberValueObject.create(1);
             const var2 = ErrorValueObject.create(ErrorType.NA);
             const result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(ErrorType.NA);
+            expect(getObjectValue(result)).toBe(ErrorType.NA);
         });
 
         it('Var1 is number, var2 is array includes error', () => {
@@ -90,7 +91,7 @@ describe('Test product function', () => {
                 column: 0,
             });
             const result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(ErrorType.VALUE);
+            expect(getObjectValue(result)).toBe(ErrorType.VALUE);
         });
 
         it('Var1 is number, var2 is array not includes error', () => {
@@ -108,7 +109,40 @@ describe('Test product function', () => {
                 column: 0,
             });
             const result = testFunction.calculate(var1, var2);
-            expect(result.getValue()).toBe(-1726.92);
+            expect(getObjectValue(result)).toBe(-1726.92);
+        });
+
+        it('More test', () => {
+            const var1 = ArrayValueObject.create({
+                calculateValueList: transformToValueObject([
+                    [true, false],
+                    ['test', null],
+                ]),
+                rowCount: 2,
+                columnCount: 2,
+                unitId: '',
+                sheetId: '',
+                row: 0,
+                column: 0,
+            });
+            const result = testFunction.calculate(var1);
+            expect(getObjectValue(result)).toBe(0);
+
+            const var2 = StringValueObject.create('4');
+            const result2 = testFunction.calculate(var1, var2);
+            expect(getObjectValue(result2)).toBe(4);
+
+            const var3 = BooleanValueObject.create(true);
+            const result3 = testFunction.calculate(var2, var3);
+            expect(getObjectValue(result3)).toBe(4);
+
+            const var4 = BooleanValueObject.create(false);
+            const result4 = testFunction.calculate(var2, var4);
+            expect(getObjectValue(result4)).toBe(0);
+
+            const var5 = StringValueObject.create('test');
+            const result5 = testFunction.calculate(var2, var5);
+            expect(getObjectValue(result5)).toBe(ErrorType.VALUE);
         });
     });
 });