Merge branch 'develop' into st31369

This commit is contained in:
Mihir Kandoi
2025-03-05 12:48:19 +05:30
committed by GitHub
38 changed files with 161055 additions and 60819 deletions

View File

@@ -19,3 +19,4 @@ jobs:
github-token: ${{ github.token }}
issue-inactive-days: 14
pr-inactive-days: 14
exclude-pr-created-before: 2025-01-01

View File

@@ -50,6 +50,7 @@ def get_group_by_asset_category_data(filters):
flt(row.accumulated_depreciation_as_on_from_date)
+ flt(row.depreciation_amount_during_the_period)
- flt(row.depreciation_eliminated_during_the_period)
- flt(row.depreciation_eliminated_via_reversal)
)
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
@@ -247,6 +248,7 @@ def get_group_by_asset_data(filters):
flt(row.accumulated_depreciation_as_on_from_date)
+ flt(row.depreciation_amount_during_the_period)
- flt(row.depreciation_eliminated_during_the_period)
- flt(row.depreciation_eliminated_via_reversal)
)
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
@@ -276,6 +278,7 @@ def get_assets_for_grouped_by_category(filters):
f"""
SELECT results.asset_category,
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal,
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.asset_category,
@@ -284,6 +287,11 @@ def get_assets_for_grouped_by_category(filters):
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then
gle.credit
else
0
end), 0) as depreciation_eliminated_via_reversal,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
@@ -307,7 +315,6 @@ def get_assets_for_grouped_by_category(filters):
a.docstatus=1
and a.company=%(company)s
and a.purchase_date <= %(to_date)s
and gle.debit != 0
and gle.is_cancelled = 0
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
{condition} {finance_book_filter}
@@ -319,6 +326,7 @@ def get_assets_for_grouped_by_category(filters):
else
a.opening_accumulated_depreciation
end), 0) as accumulated_depreciation_as_on_from_date,
0 as depreciation_eliminated_via_reversal,
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
a.opening_accumulated_depreciation
else
@@ -354,6 +362,7 @@ def get_assets_for_grouped_by_asset(filters):
f"""
SELECT results.name as asset,
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal,
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.name as name,
@@ -362,6 +371,11 @@ def get_assets_for_grouped_by_asset(filters):
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then
gle.credit
else
0
end), 0) as depreciation_eliminated_via_reversal,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
@@ -385,7 +399,6 @@ def get_assets_for_grouped_by_asset(filters):
a.docstatus=1
and a.company=%(company)s
and a.purchase_date <= %(to_date)s
and gle.debit != 0
and gle.is_cancelled = 0
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
{finance_book_filter} {condition}
@@ -397,6 +410,7 @@ def get_assets_for_grouped_by_asset(filters):
else
a.opening_accumulated_depreciation
end), 0) as accumulated_depreciation_as_on_from_date,
0 as depreciation_as_on_from_date_credit,
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
a.opening_accumulated_depreciation
else
@@ -503,6 +517,12 @@ def get_columns(filters):
"fieldtype": "Currency",
"width": 270,
},
{
"label": _("Depreciation eliminated via reversal"),
"fieldname": "depreciation_eliminated_via_reversal",
"fieldtype": "Currency",
"width": 270,
},
{
"label": _("Net Asset value as on") + " " + formatdate(filters.day_before_from_date),
"fieldname": "net_asset_value_as_on_from_date",

View File

@@ -307,6 +307,7 @@ class Deferred_Revenue_and_Expense_Report:
.where(
(inv.docstatus == 1)
& (deferred_flag_field == 1)
& (inv.company == self.filters.company)
& (
(
(self.period_list[0].from_date >= inv_item.service_start_date)

View File

@@ -2,5 +2,27 @@
// For license information, please see license.txt
frappe.query_reports["Delivered Items To Be Billed"] = {
filters: [],
filters: [
{
label: __("Company"),
fieldname: "company",
fieldtype: "Link",
options: "Company",
reqd: 1,
default: frappe.defaults.get_default("Company"),
},
{
label: __("As on Date"),
fieldname: "posting_date",
fieldtype: "Date",
reqd: 1,
default: frappe.datetime.get_today(),
},
{
label: __("Delivery Note"),
fieldname: "delivery_note",
fieldtype: "Link",
options: "Delivery Note",
},
],
};

View File

@@ -3,6 +3,7 @@
from frappe import _
from pypika import Order
from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_data
@@ -10,7 +11,7 @@ from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_d
def execute(filters=None):
columns = get_column()
args = get_args()
data = get_ordered_to_be_billed_data(args)
data = get_ordered_to_be_billed_data(args, filters)
return columns, data
@@ -76,13 +77,6 @@ def get_column():
"options": "Project",
"width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120,
},
]
@@ -92,5 +86,6 @@ def get_args():
"party": "customer",
"date": "posting_date",
"order": "name",
"order_by": "desc",
"order_by": Order.desc,
"reference_field": "delivery_note",
}

View File

@@ -4,11 +4,12 @@
import frappe
from frappe.model.meta import get_field_precision
from frappe.query_builder.functions import IfNull, Round
from erpnext import get_default_currency
def get_ordered_to_be_billed_data(args):
def get_ordered_to_be_billed_data(args, filters=None):
doctype, party = args.get("doctype"), args.get("party")
child_tab = doctype + " Item"
precision = (
@@ -18,47 +19,57 @@ def get_ordered_to_be_billed_data(args):
or 2
)
project_field = get_project_field(doctype, party)
doctype = frappe.qb.DocType(doctype)
child_doctype = frappe.qb.DocType(child_tab)
return frappe.db.sql(
"""
Select
`{parent_tab}`.name, `{parent_tab}`.{date_field},
`{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
`{child_tab}`.item_code,
`{child_tab}`.base_amount,
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
(`{child_tab}`.base_amount -
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) -
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))),
`{child_tab}`.item_name, `{child_tab}`.description,
{project_field}, `{parent_tab}`.company
from
`{parent_tab}`, `{child_tab}`
where
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
and `{parent_tab}`.status not in ('Closed', 'Completed')
and `{child_tab}`.amount > 0
and (`{child_tab}`.base_amount -
round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) -
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
order by
`{parent_tab}`.{order} {order_by}
""".format(
parent_tab="tab" + doctype,
child_tab="tab" + child_tab,
precision=precision,
party=party,
date_field=args.get("date"),
project_field=project_field,
order=args.get("order"),
order_by=args.get("order_by"),
docname = filters.get(args.get("reference_field"), None)
project_field = get_project_field(doctype, child_doctype, party)
query = (
frappe.qb.from_(doctype)
.inner_join(child_doctype)
.on(doctype.name == child_doctype.parent)
.select(
doctype.name,
doctype[args.get("date")].as_("date"),
doctype[party],
doctype[party + "_name"],
child_doctype.item_code,
child_doctype.base_amount.as_("amount"),
(child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1)).as_("billed_amount"),
(child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0)).as_("returned_amount"),
(
child_doctype.base_amount
- (child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1))
- (child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0))
).as_("pending_amount"),
child_doctype.item_name,
child_doctype.description,
project_field,
)
.where(
(doctype.docstatus == 1)
& (doctype.status.notin(["Closed", "Completed"]))
& (doctype.company == filters.get("company"))
& (doctype.posting_date <= filters.get("posting_date"))
& (child_doctype.amount > 0)
& (
child_doctype.base_amount
- Round(child_doctype.billed_amt * IfNull(doctype.conversion_rate, 1), precision)
- (child_doctype.base_rate * IfNull(child_doctype.returned_qty, 0))
)
> 0
)
.orderby(doctype[args.get("order")], order=args.get("order_by"))
)
if docname:
query = query.where(doctype.name == docname)
def get_project_field(doctype, party):
return query.run(as_dict=True)
def get_project_field(doctype, child_doctype, party):
if party == "supplier":
doctype = doctype + " Item"
return "`tab%s`.project" % (doctype)
return child_doctype.project
return doctype.project

View File

@@ -2,5 +2,27 @@
// For license information, please see license.txt
frappe.query_reports["Received Items To Be Billed"] = {
filters: [],
filters: [
{
label: __("Company"),
fieldname: "company",
fieldtype: "Link",
options: "Company",
reqd: 1,
default: frappe.defaults.get_default("Company"),
},
{
label: __("As on Date"),
fieldname: "posting_date",
fieldtype: "Date",
reqd: 1,
default: frappe.datetime.get_today(),
},
{
label: __("Purchase Receipt"),
fieldname: "purchase_receipt",
fieldtype: "Link",
options: "Purchase Receipt",
},
],
};

View File

@@ -3,6 +3,7 @@
from frappe import _
from pypika import Order
from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_data
@@ -10,7 +11,7 @@ from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_d
def execute(filters=None):
columns = get_column()
args = get_args()
data = get_ordered_to_be_billed_data(args)
data = get_ordered_to_be_billed_data(args, filters)
return columns, data
@@ -76,13 +77,6 @@ def get_column():
"options": "Project",
"width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120,
},
]
@@ -92,5 +86,6 @@ def get_args():
"party": "supplier",
"date": "posting_date",
"order": "name",
"order_by": "desc",
"order_by": Order.desc,
"reference_field": "purchase_receipt",
}

View File

@@ -93,7 +93,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Accounts Payable",
"link_count": 0,
"link_to": "Accounts Payable",
@@ -103,7 +103,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Accounts Payable Summary",
"link_count": 0,
"link_to": "Accounts Payable Summary",
@@ -113,7 +113,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Purchase Register",
"link_count": 0,
"link_to": "Purchase Register",
@@ -123,7 +123,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Item-wise Purchase Register",
"link_count": 0,
"link_to": "Item-wise Purchase Register",
@@ -133,7 +133,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Purchase Order Analysis",
"link_count": 0,
"link_to": "Purchase Order Analysis",
@@ -143,7 +143,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Received Items To Be Billed",
"link_count": 0,
"link_to": "Received Items To Be Billed",
@@ -153,7 +153,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Supplier Ledger Summary",
"link_count": 0,
"link_to": "Supplier Ledger Summary",

View File

@@ -406,7 +406,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
);
}
} else {
if (!doc.items.every((item) => item.qty == item.sco_qty)) {
if (!doc.items.every((item) => item.qty == item.subcontracted_quantity)) {
this.frm.add_custom_button(
__("Subcontracting Order"),
() => {

View File

@@ -922,7 +922,7 @@ def is_po_fully_subcontracted(po_name):
query = (
frappe.qb.from_(table)
.select(table.name)
.where((table.parent == po_name) & (table.qty != table.sco_qty))
.where((table.parent == po_name) & (table.qty != table.subcontracted_quantity))
)
return not query.run(as_dict=True)
@@ -977,7 +977,7 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
"material_request_item": "material_request_item",
},
"field_no_map": ["qty", "fg_item_qty", "amount"],
"condition": lambda item: item.qty != item.sco_qty,
"condition": lambda item: item.qty != item.subcontracted_quantity,
},
},
target_doc,

View File

@@ -1097,9 +1097,9 @@ class TestPurchaseOrder(IntegrationTestCase):
# Test - 2: Subcontracted Quantity for the PO Items of each line item should be updated accordingly
po.reload()
self.assertEqual(po.items[0].sco_qty, 5)
self.assertEqual(po.items[1].sco_qty, 0)
self.assertEqual(po.items[2].sco_qty, 12.5)
self.assertEqual(po.items[0].subcontracted_quantity, 5)
self.assertEqual(po.items[1].subcontracted_quantity, 0)
self.assertEqual(po.items[2].subcontracted_quantity, 12.5)
# Test - 3: Amount for both FG Item and its Service Item should be updated correctly based on change in Quantity
self.assertEqual(sco.items[0].amount, 2000)
@@ -1135,10 +1135,10 @@ class TestPurchaseOrder(IntegrationTestCase):
# Test - 8: Subcontracted Quantity for each PO Item should be subtracted if SCO gets cancelled
po.reload()
self.assertEqual(po.items[2].sco_qty, 25)
self.assertEqual(po.items[2].subcontracted_quantity, 25)
sco.cancel()
po.reload()
self.assertEqual(po.items[2].sco_qty, 12.5)
self.assertEqual(po.items[2].subcontracted_quantity, 12.5)
sco = make_subcontracting_order(po.name)
sco.save()

View File

@@ -26,7 +26,7 @@
"quantity_and_rate",
"qty",
"stock_uom",
"sco_qty",
"subcontracted_quantity",
"col_break2",
"uom",
"conversion_factor",
@@ -933,7 +933,7 @@
},
{
"allow_on_submit": 1,
"fieldname": "sco_qty",
"fieldname": "subcontracted_quantity",
"fieldtype": "Float",
"label": "Subcontracted Quantity",
"no_copy": 1,
@@ -941,11 +941,12 @@
"read_only": 1
}
],
"grid_page_length": 50,
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-02-18 12:35:04.432636",
"modified": "2025-03-02 16:58:26.059601",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
@@ -953,6 +954,7 @@
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "item_name",
"sort_field": "creation",
"sort_order": "DESC",

View File

@@ -82,10 +82,10 @@ class PurchaseOrderItem(Document):
sales_order_item: DF.Data | None
sales_order_packed_item: DF.Data | None
schedule_date: DF.Date
sco_qty: DF.Float
stock_qty: DF.Float
stock_uom: DF.Link
stock_uom_rate: DF.Currency
subcontracted_quantity: DF.Float
supplier_part_no: DF.Data | None
supplier_quotation: DF.Link | None
supplier_quotation_item: DF.Link | None

View File

@@ -1237,7 +1237,7 @@ class StockController(AccountsController):
child_tab.item_code,
child_tab.qty,
)
.where(parent_tab.docstatus < 2)
.where(parent_tab.docstatus == 1)
)
if self.doctype == "Purchase Invoice":

View File

@@ -104,18 +104,18 @@ class SubcontractingController(StockController):
)
if (
self.doctype == "Subcontracting Order" and not item.sc_conversion_factor
self.doctype == "Subcontracting Order" and not item.subcontracting_conversion_factor
): # this condition will only be true if user has recently updated from develop branch
service_item_qty = frappe.get_value(
"Subcontracting Order Service Item",
filters={"purchase_order_item": item.purchase_order_item, "parent": self.name},
fieldname=["qty"],
)
item.sc_conversion_factor = service_item_qty / item.qty
item.subcontracting_conversion_factor = service_item_qty / item.qty
if self.doctype not in "Subcontracting Receipt" and item.qty > flt(
get_pending_sco_qty(self.purchase_order).get(item.purchase_order_item)
/ item.sc_conversion_factor,
get_pending_subcontracted_quantity(self.purchase_order).get(item.purchase_order_item)
/ item.subcontracting_conversion_factor,
frappe.get_precision("Purchase Order Item", "qty"),
):
frappe.throw(
@@ -1138,10 +1138,14 @@ def get_item_details(items):
return item_details
def get_pending_sco_qty(po_name):
def get_pending_subcontracted_quantity(po_name):
table = frappe.qb.DocType("Purchase Order Item")
query = frappe.qb.from_(table).select(table.name, table.qty, table.sco_qty).where(table.parent == po_name)
return {item.name: item.qty - item.sco_qty for item in query.run(as_dict=True)}
query = (
frappe.qb.from_(table)
.select(table.name, table.qty, table.subcontracted_quantity)
.where(table.parent == po_name)
)
return {item.name: item.qty - item.subcontracted_quantity for item in query.run(as_dict=True)}
@frappe.whitelist()

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: info@erpnext.com\n"
"POT-Creation-Date: 2025-03-02 09:35+0000\n"
"PO-Revision-Date: 2025-03-03 04:14\n"
"PO-Revision-Date: 2025-03-05 04:06\n"
"Last-Translator: info@erpnext.com\n"
"Language-Team: Arabic\n"
"MIME-Version: 1.0\n"
@@ -47019,7 +47019,7 @@ msgstr "إرسال"
#: erpnext/templates/includes/footer/footer_extension.html:20
msgid "Sending..."
msgstr ""
msgstr "إرسال..."
#. Label of the sent (Check) field in DocType 'Project Update'
#: erpnext/projects/doctype/project_update/project_update.json

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: info@erpnext.com\n"
"POT-Creation-Date: 2025-03-02 09:35+0000\n"
"PO-Revision-Date: 2025-03-04 04:10\n"
"PO-Revision-Date: 2025-03-05 04:06\n"
"Last-Translator: info@erpnext.com\n"
"Language-Team: Bosnian\n"
"MIME-Version: 1.0\n"
@@ -37106,7 +37106,7 @@ msgstr "Postavi Knjigovodstvenu Dimenziju {} u {}"
#: erpnext/accounts/doctype/pos_profile/pos_profile.js:89
#: erpnext/accounts/doctype/sales_invoice/sales_invoice.js:752
msgid "Please set Company"
msgstr "Postavite Kompaniju"
msgstr "Postavi Kompaniju"
#: erpnext/assets/doctype/asset/depreciation.py:364
msgid "Please set Depreciation related Accounts in Asset Category {0} or Company {1}"
@@ -37119,7 +37119,7 @@ msgstr "Postavi E-poštu/Telefon za kontakt"
#: erpnext/regional/italy/utils.py:278
#, python-format
msgid "Please set Fiscal Code for the customer '%s'"
msgstr "Postavite Fiskalni Kod za Klijenta '%s'"
msgstr "Postavi Fiskalni Kod za Klijenta '%s'"
#: erpnext/regional/italy/utils.py:286
#, python-format
@@ -47894,7 +47894,7 @@ msgstr "Postavi Iz Skladišta"
#. 'Territory'
#: erpnext/setup/doctype/territory/territory.json
msgid "Set Item Group-wise budgets on this Territory. You can also include seasonality by setting the Distribution."
msgstr "Postavite budžete po grupama za ovaj Distrikt. Takođe možete uključiti sezonske varijacije postavljanjem Distribucije."
msgstr "Postavi budžete po grupama za ovaj Distrikt. Takođe možete uključiti sezonske varijacije postavljanjem Distribucije."
#. Label of the set_landed_cost_based_on_purchase_invoice_rate (Check) field in
#. DocType 'Buying Settings'
@@ -47937,7 +47937,7 @@ msgstr "Postavi Datum Knjiženja"
#: erpnext/manufacturing/doctype/bom/bom.js:898
msgid "Set Process Loss Item Quantity"
msgstr "Postavite količinu gubitka artikla u procesu"
msgstr "Postavi količinu gubitka artikla u procesu"
#: erpnext/projects/doctype/project/project.js:149
#: erpnext/projects/doctype/project/project.js:157
@@ -48042,7 +48042,7 @@ msgstr "Postavi Standard Račun Zaliha za Stalno Upravljanje Zalihama"
#: erpnext/setup/doctype/company/company.py:450
msgid "Set default {0} account for non stock items"
msgstr "Postavite Standard Račun {0} za artikle bez zaliha"
msgstr "Postavi Standard Račun {0} za artikle koji nisu na zalihama"
#. Description of the 'Fetch Value From' (Select) field in DocType 'Inventory
#. Dimension'

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: info@erpnext.com\n"
"POT-Creation-Date: 2025-03-02 09:35+0000\n"
"PO-Revision-Date: 2025-03-03 04:15\n"
"PO-Revision-Date: 2025-03-05 04:06\n"
"Last-Translator: info@erpnext.com\n"
"Language-Team: Persian\n"
"MIME-Version: 1.0\n"
@@ -18462,7 +18462,7 @@ msgstr "کارمند {0} متعلق به شرکت {1} نیست"
#: erpnext/manufacturing/doctype/job_card/job_card.py:297
msgid "Employee {0} is currently working on another workstation. Please assign another employee."
msgstr ""
msgstr "کارمند {0} در حال حاضر روی ایستگاه کاری دیگری کار می کند. لطفا کارمند دیگری را تعیین کنید."
#: erpnext/manufacturing/doctype/workstation/workstation.js:351
msgid "Employees"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: info@erpnext.com\n"
"POT-Creation-Date: 2025-03-02 09:35+0000\n"
"PO-Revision-Date: 2025-03-03 04:14\n"
"PO-Revision-Date: 2025-03-05 04:06\n"
"Last-Translator: info@erpnext.com\n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
@@ -7571,7 +7571,7 @@ msgstr "critére de restriction"
#: erpnext/setup/doctype/holiday_list/holiday_list.js:60
msgid "Based on your HR Policy, select your leave allocation period's end date"
msgstr ""
msgstr "En fonction de votre politique RH, sélectionnez la date de fin de la période d'attribution des congés"
#: erpnext/setup/doctype/holiday_list/holiday_list.js:55
msgid "Based on your HR Policy, select your leave allocation period's start date"

60470
erpnext/locale/hr.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

60470
erpnext/locale/th.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1433,7 +1433,7 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None):
},
)
def get_max_op_qty():
def get_max_operation_quantity():
from frappe.query_builder.functions import Sum
table = frappe.qb.DocType("Job Card")
@@ -1449,7 +1449,7 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None):
)
return min([d.qty for d in query.run(as_dict=True)], default=0)
def get_utilised_cc():
def get_utilised_corrective_cost():
from frappe.query_builder.functions import Sum
table = frappe.qb.DocType("Stock Entry")
@@ -1479,15 +1479,15 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None):
)
)
):
max_qty = get_max_op_qty() - work_order.produced_qty
remaining_cc = work_order.corrective_operation_cost - get_utilised_cc()
max_qty = get_max_operation_quantity() - work_order.produced_qty
remaining_corrective_cost = work_order.corrective_operation_cost - get_utilised_corrective_cost()
stock_entry.append(
"additional_costs",
{
"expense_account": expense_account,
"description": "Corrective Operation Cost",
"has_corrective_cost": 1,
"amount": remaining_cc / max_qty * flt(stock_entry.fg_completed_qty),
"amount": remaining_corrective_cost / max_qty * flt(stock_entry.fg_completed_qty),
},
)

View File

@@ -260,6 +260,7 @@ erpnext.patches.v14_0.show_loan_management_deprecation_warning
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True)
erpnext.patches.v14_0.update_proprietorship_to_individual
erpnext.patches.v15_0.rename_subcontracting_fields
[post_model_sync]
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
@@ -403,4 +404,6 @@ erpnext.patches.v14_0.disable_add_row_in_gross_profit
erpnext.patches.v14_0.update_posting_datetime
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference
erpnext.patches.v15_0.recalculate_amount_difference_field
erpnext.patches.v15_0.rename_sla_fields
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
erpnext.patches.v15_0.update_query_report

View File

@@ -0,0 +1,13 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import rename_fieldname
from frappe.model.utils.rename_field import rename_field
def execute():
doctypes = frappe.get_all("Service Level Agreement", pluck="document_type")
for doctype in doctypes:
rename_fieldname(doctype + "-resolution_by", "sla_resolution_by")
rename_fieldname(doctype + "-resolution_date", "sla_resolution_date")
rename_field("Issue", "resolution_by", "sla_resolution_by")
rename_field("Issue", "resolution_date", "sla_resolution_date")

View File

@@ -0,0 +1,7 @@
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
rename_field("Purchase Order Item", "sco_qty", "subcontracted_quantity")
rename_field("Subcontracting Order Item", "sc_conversion_factor", "subcontracting_conversion_factor")

View File

@@ -0,0 +1,25 @@
import frappe
def execute():
reports = [
"Accounts Payable",
"Accounts Payable Summary",
"Purchase Register",
"Item-wise Purchase Register",
"Purchase Order Analysis",
"Received Items To Be Billed",
"Supplier Ledger Summary",
]
frappe.db.set_value(
"Workspace Link",
{
"parent": "Payables",
"link_type": "Report",
"type": "Link",
"link_to": ["in", reports],
"is_query_report": 0,
},
"is_query_report",
1,
)

View File

@@ -1063,7 +1063,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
ignore_permissions=True,
)
dn_item.qty = flt(sre.reserved_qty) * flt(dn_item.get("conversion_factor", 1))
dn_item.qty = flt(sre.reserved_qty) / flt(dn_item.get("conversion_factor", 1))
dn_item.warehouse = sre.warehouse
if sre.reservation_based_on == "Serial and Batch" and (sre.has_serial_no or sre.has_batch_no):

View File

@@ -16,14 +16,14 @@ frappe.ui.form.on("Subcontracting Order Item", {
service_item.doctype,
service_item.name,
"qty",
row.qty * row.sc_conversion_factor
row.qty * row.subcontracting_conversion_factor
);
frappe.model.set_value(service_item.doctype, service_item.name, "fg_item_qty", row.qty);
frappe.model.set_value(
service_item.doctype,
service_item.name,
"amount",
row.qty * row.sc_conversion_factor * service_item.rate
row.qty * row.subcontracting_conversion_factor * service_item.rate
);
},
before_items_remove(frm, cdt, cdn) {

View File

@@ -119,12 +119,12 @@ class SubcontractingOrder(SubcontractingController):
def on_submit(self):
self.update_prevdoc_status()
self.update_status()
self.update_sco_qty_in_po()
self.update_subcontracted_quantity_in_po()
def on_cancel(self):
self.update_prevdoc_status()
self.update_status()
self.update_sco_qty_in_po(cancel=True)
self.update_subcontracted_quantity_in_po(cancel=True)
def validate_purchase_order_for_subcontracting(self):
if self.purchase_order:
@@ -162,7 +162,7 @@ class SubcontractingOrder(SubcontractingController):
item = next(
item for item in self.items if item.purchase_order_item == service_item.purchase_order_item
)
service_item.qty = item.qty * item.sc_conversion_factor
service_item.qty = item.qty * item.subcontracting_conversion_factor
service_item.fg_item_qty = item.qty
service_item.amount = service_item.qty * service_item.rate
@@ -250,7 +250,7 @@ class SubcontractingOrder(SubcontractingController):
item = frappe.get_doc("Item", si.fg_item)
po_item = frappe.get_doc("Purchase Order Item", si.purchase_order_item)
available_qty = po_item.qty - po_item.sco_qty
available_qty = po_item.qty - po_item.subcontracted_quantity
if available_qty == 0:
continue
@@ -276,7 +276,7 @@ class SubcontractingOrder(SubcontractingController):
"schedule_date": self.schedule_date,
"description": item.description,
"qty": si.fg_item_qty,
"sc_conversion_factor": conversion_factor,
"subcontracting_conversion_factor": conversion_factor,
"stock_uom": item.stock_uom,
"bom": bom,
"purchase_order_item": si.purchase_order_item,
@@ -330,10 +330,14 @@ class SubcontractingOrder(SubcontractingController):
self.update_ordered_qty_for_subcontracting()
self.update_reserved_qty_for_subcontracting()
def update_sco_qty_in_po(self, cancel=False):
def update_subcontracted_quantity_in_po(self, cancel=False):
for service_item in self.service_items:
doc = frappe.get_doc("Purchase Order Item", service_item.purchase_order_item)
doc.sco_qty = (doc.sco_qty + service_item.qty) if not cancel else (doc.sco_qty - service_item.qty)
doc.subcontracted_quantity = (
(doc.subcontracted_quantity + service_item.qty)
if not cancel
else (doc.subcontracted_quantity - service_item.qty)
)
doc.save()

View File

@@ -55,7 +55,7 @@
"section_break_34",
"purchase_order_item",
"page_break",
"sc_conversion_factor"
"subcontracting_conversion_factor"
],
"fields": [
{
@@ -403,18 +403,19 @@
"fieldtype": "Column Break"
},
{
"fieldname": "sc_conversion_factor",
"fieldname": "subcontracting_conversion_factor",
"fieldtype": "Float",
"hidden": 1,
"label": "SC Conversion Factor",
"label": "Subcontracting Conversion Factor",
"read_only": 1
}
],
"grid_page_length": 50,
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-12-13 13:35:28.935898",
"modified": "2025-03-02 17:05:28.386492",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Item",
@@ -422,6 +423,7 @@
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "item_name",
"sort_field": "creation",
"sort_order": "DESC",

View File

@@ -42,10 +42,10 @@ class SubcontractingOrderItem(Document):
received_qty: DF.Float
returned_qty: DF.Float
rm_cost_per_qty: DF.Currency
sc_conversion_factor: DF.Float
schedule_date: DF.Date | None
service_cost_per_qty: DF.Currency
stock_uom: DF.Link
subcontracting_conversion_factor: DF.Float
warehouse: DF.Link
# end: auto-generated types

View File

@@ -27,7 +27,7 @@
"reset_service_level_agreement",
"cb",
"agreement_status",
"resolution_by",
"sla_resolution_by",
"service_level_agreement_creation",
"on_hold_since",
"total_hold_time",
@@ -41,7 +41,7 @@
"column_break1",
"opening_date",
"opening_time",
"resolution_date",
"sla_resolution_date",
"resolution_time",
"user_resolution_time",
"additional_info",
@@ -176,13 +176,6 @@
"options": "fa fa-pushpin",
"read_only": 1
},
{
"depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;",
"fieldname": "resolution_by",
"fieldtype": "Datetime",
"label": "Resolution By",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "response",
@@ -287,16 +280,6 @@
"oldfieldtype": "Time",
"read_only": 1
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "resolution_date",
"fieldtype": "Datetime",
"label": "Resolution Date",
"no_copy": 1,
"oldfieldname": "resolution_date",
"oldfieldtype": "Date",
"read_only": 1
},
{
"fieldname": "content_type",
"fieldtype": "Data",
@@ -386,12 +369,29 @@
"fieldtype": "Duration",
"label": "First Response Time",
"read_only": 1
},
{
"depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;",
"fieldname": "sla_resolution_by",
"fieldtype": "Datetime",
"label": "Resolution By",
"read_only": 1
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "sla_resolution_date",
"fieldtype": "Datetime",
"label": "Resolution Date",
"no_copy": 1,
"oldfieldname": "resolution_date",
"oldfieldtype": "Date",
"read_only": 1
}
],
"icon": "fa fa-ticket",
"idx": 7,
"links": [],
"modified": "2024-03-27 13:09:52.921791",
"modified": "2025-02-18 21:18:52.797745",
"modified_by": "Administrator",
"module": "Support",
"name": "Issue",

View File

@@ -48,13 +48,13 @@ class Issue(Document):
priority: DF.Link | None
project: DF.Link | None
raised_by: DF.Data | None
resolution_by: DF.Datetime | None
resolution_date: DF.Datetime | None
resolution_details: DF.TextEditor | None
resolution_time: DF.Duration | None
response_by: DF.Datetime | None
service_level_agreement: DF.Link | None
service_level_agreement_creation: DF.Datetime | None
sla_resolution_by: DF.Datetime | None
sla_resolution_date: DF.Datetime | None
status: DF.Literal["Open", "Replied", "On Hold", "Resolved", "Closed"]
subject: DF.Data
total_hold_time: DF.Duration | None

View File

@@ -33,48 +33,48 @@ class TestIssue(TestSetUp):
issue = make_issue(creation, "_Test Customer", 1)
self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00"))
self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00"))
# make issue with customer_group specific SLA
create_customer("__Test Customer", "_Test SLA Customer Group", "__Test SLA Territory")
issue = make_issue(creation, "__Test Customer", 2)
self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00"))
self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00"))
# make issue with territory specific SLA
create_customer("___Test Customer", "__Test SLA Customer Group", "_Test SLA Territory")
issue = make_issue(creation, "___Test Customer", 3)
self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00"))
self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00"))
# make issue with default SLA
issue = make_issue(creation=creation, index=4)
self.assertEqual(issue.response_by, get_datetime("2019-03-04 16:00"))
self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 18:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 18:00"))
# make issue with default SLA before working hours
creation = get_datetime("2019-03-04 7:00")
issue = make_issue(creation=creation, index=5)
self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00"))
self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 16:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 16:00"))
# make issue with default SLA after working hours
creation = get_datetime("2019-03-04 20:00")
issue = make_issue(creation, index=6)
self.assertEqual(issue.response_by, get_datetime("2019-03-06 14:00"))
self.assertEqual(issue.resolution_by, get_datetime("2019-03-06 16:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-06 16:00"))
# make issue with default SLA next day
creation = get_datetime("2019-03-04 14:00")
issue = make_issue(creation=creation, index=7)
self.assertEqual(issue.response_by, get_datetime("2019-03-04 18:00"))
self.assertEqual(issue.resolution_by, get_datetime("2019-03-06 12:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-06 12:00"))
frappe.flags.current_time = get_datetime("2019-03-04 15:00")
issue.reload()
@@ -98,7 +98,7 @@ class TestIssue(TestSetUp):
issue.save()
self.assertEqual(issue.on_hold_since, frappe.flags.current_time)
self.assertFalse(issue.resolution_by)
self.assertFalse(issue.sla_resolution_by)
creation = get_datetime("2020-03-04 5:00")
frappe.flags.current_time = get_datetime("2020-03-04 5:00")
@@ -106,7 +106,7 @@ class TestIssue(TestSetUp):
issue.reload()
self.assertEqual(flt(issue.total_hold_time, 2), 2700)
self.assertEqual(issue.resolution_by, get_datetime("2020-03-04 16:45"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2020-03-04 16:45"))
creation = get_datetime("2020-03-04 5:05")
create_communication(issue.name, "test@admin.com", "Sent", creation)
@@ -140,8 +140,8 @@ class TestIssue(TestSetUp):
issue.status = "Closed"
issue.save()
self.assertEqual(issue.resolution_by, get_datetime("2021-11-22 06:00:00"))
self.assertEqual(issue.resolution_date, get_datetime("2021-11-22 01:00:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-22 06:00:00"))
self.assertEqual(issue.sla_resolution_date, get_datetime("2021-11-22 01:00:00"))
self.assertEqual(issue.agreement_status, "Fulfilled")
def test_issue_open_after_closed(self):
@@ -153,7 +153,7 @@ class TestIssue(TestSetUp):
create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time)
self.assertEqual(issue.agreement_status, "First Response Due")
self.assertEqual(issue.response_by, get_datetime("2021-11-01 17:00"))
self.assertEqual(issue.resolution_by, get_datetime("2021-11-01 19:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-01 19:00"))
# Replied on → 2 pm
frappe.flags.current_time = get_datetime("2021-11-01 14:00")
@@ -173,7 +173,7 @@ class TestIssue(TestSetUp):
# Hold Time + 1 Hrs
self.assertEqual(issue.total_hold_time, 3600)
# Resolution By should increase by one hrs
self.assertEqual(issue.resolution_by, get_datetime("2021-11-01 20:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-01 20:00"))
# Replied on → 4 pm, Open → 1 hr, Resolution Due → 8 pm
frappe.flags.current_time = get_datetime("2021-11-01 16:00")
@@ -190,9 +190,9 @@ class TestIssue(TestSetUp):
# Hold Time + 6 Hrs
self.assertEqual(issue.total_hold_time, 3600 + 21600)
# Resolution By should increase by 6 hrs
self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 02:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 02:00"))
self.assertEqual(issue.agreement_status, "Fulfilled")
self.assertEqual(issue.resolution_date, frappe.flags.current_time)
self.assertEqual(issue.sla_resolution_date, frappe.flags.current_time)
# Customer Open → 3 am i.e after resolution by is crossed
frappe.flags.current_time = get_datetime("2021-11-02 03:00")
@@ -201,17 +201,17 @@ class TestIssue(TestSetUp):
# Since issue was Resolved, Resolution By should be increased by 5 hrs (3am - 10pm)
self.assertEqual(issue.total_hold_time, 3600 + 21600 + 18000)
# Resolution By should increase by 5 hrs
self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 07:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 07:00"))
self.assertEqual(issue.agreement_status, "Resolution Due")
self.assertFalse(issue.resolution_date)
self.assertFalse(issue.sla_resolution_date)
# We Closed → 4 am, SLA should be Fulfilled
frappe.flags.current_time = get_datetime("2021-11-02 04:00")
issue.status = "Closed"
issue.save()
self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 07:00"))
self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 07:00"))
self.assertEqual(issue.agreement_status, "Fulfilled")
self.assertEqual(issue.resolution_date, frappe.flags.current_time)
self.assertEqual(issue.sla_resolution_date, frappe.flags.current_time)
def test_recording_of_assignment_on_first_reponse_failure(self):
from frappe.desk.form.assign_to import add as add_assignment

View File

@@ -514,7 +514,7 @@ def apply(doc, method=None):
def remove_sla_if_applied(doc):
doc.service_level_agreement = None
doc.response_by = None
doc.resolution_by = None
doc.sla_resolution_by = None
def process_sla(doc, sla):
@@ -557,7 +557,7 @@ def handle_status_change(doc, apply_sla_for_resolution):
# In case issue was closed and after few days it has been opened
# The hold time should be calculated from resolution_date
on_hold_since = doc.resolution_date or doc.on_hold_since
on_hold_since = doc.sla_resolution_date or doc.on_hold_since
if on_hold_since:
current_hold_hours = time_diff_in_seconds(now_time, on_hold_since)
doc.total_hold_time = (doc.total_hold_time or 0) + current_hold_hours
@@ -582,7 +582,7 @@ def handle_status_change(doc, apply_sla_for_resolution):
# Open to Closed
if is_open_status(prev_status) and is_fulfilled_status(doc.status):
# Issue is closed -> Set resolution_date
doc.resolution_date = now_time
doc.sla_resolution_date = now_time
set_resolution_time(doc)
# Closed to Open
@@ -606,7 +606,7 @@ def handle_status_change(doc, apply_sla_for_resolution):
calculate_hold_hours()
# Issue is closed -> Set resolution_date
if apply_sla_for_resolution:
doc.resolution_date = now_time
doc.sla_resolution_date = now_time
set_resolution_time(doc)
@@ -713,7 +713,7 @@ def get_support_days(service_level):
def set_resolution_time(doc):
start_date_time = get_datetime(doc.get("service_level_agreement_creation") or doc.creation)
if doc.meta.has_field("resolution_time"):
doc.resolution_time = time_diff_in_seconds(doc.resolution_date, start_date_time)
doc.resolution_time = time_diff_in_seconds(doc.sla_resolution_date, start_date_time)
# total time taken by a user to close the issue apart from wait_time
if not doc.meta.has_field("user_resolution_time"):
@@ -737,7 +737,7 @@ def set_resolution_time(doc):
pending_time.append(wait_time)
total_pending_time = sum(pending_time)
resolution_time_in_secs = time_diff_in_seconds(doc.resolution_date, start_date_time)
resolution_time_in_secs = time_diff_in_seconds(doc.sla_resolution_date, start_date_time)
doc.user_resolution_time = resolution_time_in_secs - total_pending_time
@@ -791,8 +791,8 @@ def reset_service_level_agreement(doctype: str, docname: str, reason, user):
def reset_resolution_metrics(doc):
if doc.meta.has_field("resolution_date"):
doc.resolution_date = None
if doc.meta.has_field("sla_resolution_date"):
doc.sla_resolution_date = None
if doc.meta.has_field("resolution_time"):
doc.resolution_time = None
@@ -859,8 +859,8 @@ def on_communication_update(doc, status):
def reset_expected_response_and_resolution(doc):
if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
doc.response_by = None
if doc.meta.has_field("resolution_by") and not doc.get("resolution_date"):
doc.resolution_by = None
if doc.meta.has_field("sla_resolution_by") and not doc.get("sla_resolution_date"):
doc.sla_resolution_by = None
def set_response_by(doc, start_date_time, priority):
@@ -877,12 +877,14 @@ def set_response_by(doc, start_date_time, priority):
def set_resolution_by(doc, start_date_time, priority):
if doc.meta.has_field("resolution_by"):
doc.resolution_by = get_expected_time_for(
if doc.meta.has_field("sla_resolution_by"):
doc.sla_resolution_by = get_expected_time_for(
parameter="resolution", service_level=priority, start_date_time=start_date_time
)
if doc.meta.has_field("total_hold_time") and doc.get("total_hold_time"):
doc.resolution_by = add_to_date(doc.resolution_by, seconds=round(doc.get("total_hold_time")))
doc.sla_resolution_by = add_to_date(
doc.sla_resolution_by, seconds=round(doc.get("total_hold_time"))
)
def record_assigned_users_on_failure(doc):
@@ -941,7 +943,7 @@ def get_service_level_agreement_fields():
"read_only": 1,
},
{
"fieldname": "resolution_by",
"fieldname": "sla_resolution_by",
"fieldtype": "Datetime",
"label": "Resolution By",
"read_only": 1,
@@ -955,7 +957,7 @@ def get_service_level_agreement_fields():
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "resolution_date",
"fieldname": "sla_resolution_date",
"fieldtype": "Datetime",
"label": "Resolution Date",
"no_copy": 1,
@@ -975,9 +977,9 @@ def update_agreement_status(doc, apply_sla_for_resolution):
if apply_sla_for_resolution:
if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
doc.agreement_status = "First Response Due"
elif doc.meta.has_field("resolution_date") and not doc.get("resolution_date"):
elif doc.meta.has_field("sla_resolution_date") and not doc.get("sla_resolution_date"):
doc.agreement_status = "Resolution Due"
elif get_datetime(doc.get("resolution_date")) <= get_datetime(doc.get("resolution_by")):
elif get_datetime(doc.get("sla_resolution_date")) <= get_datetime(doc.get("sla_resolution_by")):
doc.agreement_status = "Fulfilled"
else:
doc.agreement_status = "Failed"

View File

@@ -227,7 +227,7 @@ class TestServiceLevelAgreement(IntegrationTestCase):
self.assertEqual(lead.service_level_agreement, lead_sla.name)
self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))
self.assertEqual(lead.resolution_by, datetime.datetime(2019, 3, 4, 18, 0))
self.assertEqual(lead.sla_resolution_by, datetime.datetime(2019, 3, 4, 18, 0))
frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
lead.reload()
@@ -268,7 +268,7 @@ class TestServiceLevelAgreement(IntegrationTestCase):
lead.reload()
self.assertEqual(flt(lead.total_hold_time, 2), 3000)
self.assertEqual(lead.resolution_by, datetime.datetime(2020, 3, 4, 16, 50))
self.assertEqual(lead.sla_resolution_by, datetime.datetime(2020, 3, 4, 16, 50))
def test_failed_sla_for_response_only(self):
doctype = "Lead"