نعم، الحل الأمثل لهذه الحالة هو استخدام عنصر مسار الكيان EAGroovyAction، فهو يتيح تنفيذ منطق مخصّص عند حفظ أو ترحيل فاتورة المشتريات دون الحاجة لأي تعديل على النظام نفسه.
فكرة العمل
عند تشغيل الأكشن على فاتورة المشتريات، يمر على سطور الفاتورة، ولكل سطر به قيمة في حقل سعر البيع الجديد n2 (فقط إذا كانت له قيمة):
- يحمّل قائمة الأسعار المسجّلة بالكود
SPL00003.
- إذا كان نفس الصنف موجوداً في القائمة، يعدّل حقل السعر الافتراضي إلى القيمة الجديدة القادمة من الفاتورة.
- إذا لم يكن الصنف موجوداً، يضيف له سطراً جديداً بالسعر الجديد.
السكربت
package com.namasoft.entiyactions
import com.namasoft.common.utilities.ObjectChecker
import com.namasoft.infra.domainbase.datafields.DecimalDF
import com.namasoft.infra.domainbase.datafields.EntityCodeDF
import com.namasoft.infra.domainbase.datafields.LongTextDF
import com.namasoft.infra.domainbase.entity.base.EntityAction
import com.namasoft.infra.domainbase.entity.base.EntityMediator
import com.namasoft.infra.domainbase.persistence.repos.FilterOnDimensions
import com.namasoft.infra.domainbase.persistence.repos.Persister
import com.namasoft.infra.domainbase.util.Result
import com.namasoft.modules.supplychain.domain.details.BasicSCDocumentLine
import com.namasoft.modules.supplychain.domain.details.SalesPriceListLine
import com.namasoft.modules.supplychain.domain.entities.BasicSCDocument
import com.namasoft.modules.supplychain.domain.entities.SalesPriceList
class EAUpdatePriceListFromPurchaseInvoice implements EntityAction<BasicSCDocument> {
private static final String PRICE_LIST_CODE = "SPL00003"
boolean startedEditing = false
@Override
Result doAction(BasicSCDocument object, LongTextDF... parameters) {
Result result = Result.createAccumulatingResult()
// تحميل قائمة الأسعار المطلوبة بالكود
SalesPriceList priceList = Persister.findByBusinessCode(
SalesPriceList.class, EntityCodeDF.fromString(PRICE_LIST_CODE), FilterOnDimensions.No)
if (ObjectChecker.isEmptyOrNull(priceList)) {
Result.createFailureResult("لا توجد قائمة أسعار بالكود {0}", PRICE_LIST_CODE)
.addToAccumulatingResult(result)
return result
}
for (BasicSCDocumentLine line : (object.getDetails() as List<BasicSCDocumentLine>)) {
// نتعامل فقط مع السطور التي بها قيمة في حقل سعر البيع الجديد (n2) وبها صنف
if (DecimalDF.isEmptyOrZero(line.getN2()) || ObjectChecker.isEmptyOrNull(line.getItem()))
continue
SalesPriceListLine existingLine = findExistingLine(priceList, line)
if (existingLine != null) {
// الصنف موجود: نعدّل السعر الافتراضي فقط
if (ObjectChecker.areNotEqual(existingLine.getDefaultPrice(), line.getN2())) {
if (!startedEditing) {
startEditingIfNeeded(priceList, result)
existingLine = findExistingLine(priceList, line)
}
if (result.failed())
return result
existingLine.setDefaultPrice(line.getN2())
}
} else {
startEditingIfNeeded(priceList, result)
if (result.failed())
return result
// الصنف غير موجود: نضيف سطراً جديداً بالصنف والسعر الجديد
SalesPriceListLine newLine = new SalesPriceListLine()
newLine.setItem(line.getItem().getItem())
newLine.setDefaultPrice(line.getN2())
priceList.getDetails().add(newLine)
}
}
if (startedEditing)
EntityMediator.commitFromBusinessAction(priceList).addToAccumulatingResult(result)
return result
}
private static SalesPriceListLine findExistingLine(SalesPriceList priceList, BasicSCDocumentLine line) {
return priceList.getDetails().stream()
.filter { ObjectChecker.areEqual(it.getItem(), line.getItem().getItem()) }
.findFirst().orElse(null)
}
void startEditingIfNeeded(SalesPriceList priceList, Result result) {
if (startedEditing)
return
startedEditing = true
// الدخول في وضع التعديل قبل تغيير القائمة
priceList.startEditing().addToAccumulatingResult(result)
}
}
كيفية الربط
أنشئ مسار كيان (Entity Flow) على فاتورة المشتريات، وأضف فيه سطراً من نوع EAGroovyAction، وضع الكود أعلاه في مُدخل السكربت، وحدّد متى يعمل (مثلاً بعد الحفظ أو بعد الترحيل).
شرح كامل لعنصر EAGroovyAction (كما طلبت، لاستخدامه في مهام أخرى):
https://docs.namasoft.com/entity-flows/core/EAGroovyAction.html
نصيحة مهمة
كتابة سكربتات Groovy من الصفر ليست مهمة سهلة، ولا يُنصح بأن يقوم بها فريق الدعم بنفسه، فهي تتطلب معرفة دقيقة بأسماء الكيانات والحقول الفعلية وبنمط أكواد Nama، وأي خطأ بسيط قد يؤدي إلى نتائج غير صحيحة على البيانات الفعلية. لذلك الأفضل دائماً أن يطلب فريق الدعم السكربت من فريق التطوير المختص بدلاً من كتابته بنفسه.
وحتى عند الاستعانة بمساعدات الذكاء الاصطناعي — رغم فائدتها في تسريع الكتابة وتوفير نقطة انطلاق — فإنها قد ترتكب أخطاء جوهرية. فالنسخة الأولى من هذا السكربت مثلاً احتوت على خطأين تم اكتشافهما وتصحيحهما أثناء مراجعة أحد المطوّرين: تعيين الصنف بطريقة خاطئة (بدون الوصول إلى مرجع الصنف الصحيح)، ونسخ حقل الكمية بشكل غير سليم. ولهذا تبقى مراجعة المطوّر المختص وتجربته للسكربت خطوة أساسية لا غنى عنها قبل الاعتماد على أي كود.
يمكن لفريق التطوير تسريع العمل بالاستعانة بنموذج البيانات على dm.namasoft.com (لمعرفة الأسماء الصحيحة للحقول والكيانات) وبمهارة "Groovy Writer" المُعدّة لهذا الغرض (https://docs.namasoft.com/skills/groovy-writer.md)، مع بقاء المراجعة البشرية ضرورية.
ملاحظة: يُنصح دائماً بتجربة السكربت على بيئة اختبار أولاً قبل تشغيله على البيانات الفعلية، والتأكد من أن السطر المُضاف لقائمة الأسعار يحتوي على كل البيانات التي يتطلبها إعداد قائمة الأسعار لديك (مثل الوحدة إن لزم).