Compare commits

..

47 Commits

Author SHA1 Message Date
Frappe PR Bot
f182fc1f8e chore(release): Bumped to Version 13.51.1
## [13.51.1](https://github.com/frappe/erpnext/compare/v13.51.0...v13.51.1) (2023-06-07)

### Bug Fixes

* Interest Accrual on Loan Topup ([#35555](https://github.com/frappe/erpnext/issues/35555)) ([1415f40](1415f40dfb))
* Task gantt popup style ([5544801](55448017d7))
2023-06-07 06:18:08 +00:00
Deepesh Garg
1897d6f214 Merge pull request #35570 from frappe/version-13-hotfix
chore: release v13
2023-06-07 11:46:43 +05:30
mergify[bot]
1415f40dfb fix: Interest Accrual on Loan Topup (#35555)
fix: Interest Accrual on Loan Topup (#35555)

* fix: Interest Accrual on Loan Topup

* chore: CI

* chore: Ignore test

(cherry picked from commit 2ffcca6f10)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-07 10:11:54 +05:30
Suraj Shetty
09cf050b0d Merge pull request #35532 from frappe/mergify/bp/version-13-hotfix/pr-35530
fix: Task gantt popup style and info (backport #35530)
2023-06-02 11:11:35 +05:30
Suraj Shetty
55448017d7 fix: Task gantt popup style
(cherry picked from commit f7b2d103e7)
2023-06-02 05:39:53 +00:00
ruthra kumar
202513ae6a Merge pull request #35520 from frappe/mergify/bp/version-13-hotfix/pr-35112
refactor(Gross Profit): simplify group by invoice logic (backport #35112)
2023-06-01 15:13:02 +05:30
ruthra kumar
e3a8a8d195 refactor: simplify group by invoice logic
(cherry picked from commit 092c4b4c58)
2023-06-01 09:05:35 +00:00
Sagar Vora
169af8f9f8 Merge pull request #35502 from frappe/mergify/bp/version-13-hotfix/pr-35500 2023-05-31 14:46:24 +05:30
Sagar Vora
f0580b0e4d chore: remove whitelisting for method not accessed from UI
(cherry picked from commit 517d8a03ec)
2023-05-31 09:15:46 +00:00
Frappe PR Bot
b5b34c14b2 chore(release): Bumped to Version 13.51.0
# [13.51.0](https://github.com/frappe/erpnext/compare/v13.50.6...v13.51.0) (2023-05-31)

### Bug Fixes

* force to do reposting for cancelled document ([0228933](022893391b))
* **Gross Profit:** 'company' column is ambiguous in filter ([270eb1d](270eb1db4d))
* incorrect `POS Reserved Qty` in `Stock Projected Qty` Report ([#35437](https://github.com/frappe/erpnext/issues/35437)) ([139a193](139a193f1d))
* incorrect depr schedule and posting dates on selling of existing assets [v13] ([#35404](https://github.com/frappe/erpnext/issues/35404)) ([20d3381](20d3381010))
* monthly WDV depr schedule for existing assets [v13] ([#35461](https://github.com/frappe/erpnext/issues/35461)) ([6f43829](6f43829c32))
* retention stock entry: grab conversion factor from source ([d8dd22a](d8dd22adaf))

### Features

* Allow ceil & floor functions in salary slip formulae ([#35475](https://github.com/frappe/erpnext/issues/35475)) ([63fba9d](63fba9db39))
2023-05-31 05:49:42 +00:00
Deepesh Garg
839a1f0454 Merge pull request #35474 from frappe/version-13-hotfix
chore: release v13
2023-05-31 11:14:32 +05:30
Rucha Mahabal
63fba9db39 feat: Allow ceil & floor functions in salary slip formulae (#35475) 2023-05-30 16:11:44 +05:30
Sagar Sharma
00fd08c7bc Merge pull request #35468 from frappe/mergify/bp/version-13-hotfix/pr-35459
fix: retention stock entry: grab conversion factor from source (backport #35459)
2023-05-30 11:30:41 +05:30
Marc de Lima Lucio
d8dd22adaf fix: retention stock entry: grab conversion factor from source
(cherry picked from commit 6954f538c9)
2023-05-30 05:36:33 +00:00
Anand Baburajan
6f43829c32 fix: monthly WDV depr schedule for existing assets [v13] (#35461)
fix: monthly wdv depr schedule for existing assets
2023-05-29 23:18:04 +05:30
Sagar Sharma
3e95d56240 Merge pull request #35456 from frappe/mergify/bp/version-13-hotfix/pr-35328
fix: force to do reposting for cancelled document (backport #35328)
2023-05-29 15:56:54 +05:30
s-aga-r
44cb62824d chore: conflicts 2023-05-29 15:22:22 +05:30
Rohit Waghchaure
022893391b fix: force to do reposting for cancelled document
(cherry picked from commit 6e661e7c0e)

# Conflicts:
#	erpnext/controllers/stock_controller.py
2023-05-29 09:38:35 +00:00
mergify[bot]
139a193f1d fix: incorrect POS Reserved Qty in Stock Projected Qty Report (#35437)
* fix: incorrect `POS Reserved Qty` in `Stock Projected Qty` Report

(cherry picked from commit 027de41600)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/pos_invoice.py

* chore: `conflicts`

---------

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2023-05-29 09:24:20 +05:30
ruthra kumar
4f5ee6876d Merge pull request #35420 from frappe/mergify/bp/version-13-hotfix/pr-35417
fix(Gross Profit): 'company' column is ambiguous in filter (backport #35417)
2023-05-25 16:44:56 +05:30
ruthra kumar
270eb1db4d fix(Gross Profit): 'company' column is ambiguous in filter
(cherry picked from commit 448712f719)
2023-05-25 09:37:39 +00:00
Anand Baburajan
20d3381010 fix: incorrect depr schedule and posting dates on selling of existing assets [v13] (#35404)
fix: calc depr amount properly on selling of existing assets and fix incorrect posting dates
2023-05-24 14:24:18 +05:30
Frappe PR Bot
fd04bd0f72 chore(release): Bumped to Version 13.50.6
## [13.50.6](https://github.com/frappe/erpnext/compare/v13.50.5...v13.50.6) (2023-05-24)

### Bug Fixes

* allow over-payment against SO ([#35079](https://github.com/frappe/erpnext/issues/35079)) ([eb243c2](eb243c2470))
* bypass flag in Customer Group wasn't effective ([f0c9d89](f0c9d89aab))
* change field-type to remove currency field from total row in export ([f65be40](f65be40037))
* consider 0 if rate/qty are null (backport [#35338](https://github.com/frappe/erpnext/issues/35338)) ([#35341](https://github.com/frappe/erpnext/issues/35341)) ([387f8b9](387f8b9e1a))
* depreciation schedule for existing assets [v14] (backport [#35255](https://github.com/frappe/erpnext/issues/35255)) ([#35347](https://github.com/frappe/erpnext/issues/35347)) ([7506132](7506132861))
* error while saving job card ([d6427cf](d6427cfe53))
* get_query filters ([2aa7729](2aa7729243))
* Incorrect Earned Leaves Proration ([#35156](https://github.com/frappe/erpnext/issues/35156)) ([dc04b24](dc04b24234))
* linter ([0a42e6f](0a42e6ff0f))
* non manufacturing items/fixed asset items in BOM ([66ba74f](66ba74f3fc))
* Pick List TypeError ([137898d](137898d55d))
* tds incorrectly calculated for invoice that are below threshold ([6c170ab](6c170abdf9))
* **test:** cumulative threshold checks ([06deecb](06deecbd92))
* use flt instead of mandatory field ([f63b866](f63b866de3))
2023-05-24 03:03:13 +00:00
Deepesh Garg
166ec0e58c Merge pull request #35393 from frappe/version-13-hotfix
chore: release v13
2023-05-24 08:31:15 +05:30
Saurabh
1e1dddfe6c Merge pull request #35387 from saurabh6790/minor-fix
fix: change field-type to remove currency field from total row in export
2023-05-23 18:53:45 +05:30
Saurabh
0a42e6ff0f fix: linter 2023-05-23 15:36:23 +05:30
Sagar Sharma
97f9c0d53f Merge pull request #35391 from frappe/mergify/bp/version-13-hotfix/pr-35303
fix: Pick List TypeError (backport #35303)
2023-05-23 15:05:41 +05:30
Sagar Sharma
137898d55d fix: Pick List TypeError
(cherry picked from commit a111917114)
2023-05-23 08:51:32 +00:00
Saurabh
f65be40037 fix: change field-type to remove currency field from total row in export 2023-05-23 12:39:20 +05:30
Sagar Sharma
75f4a616f1 Merge pull request #35384 from frappe/mergify/bp/version-13-hotfix/pr-35380
fix: TypeError while saving Job card (backport #35380)
2023-05-23 10:59:15 +05:30
Sagar Sharma
8d97f8b0b7 chore: conflicts 2023-05-23 10:54:27 +05:30
vishnu
f63b866de3 fix: use flt instead of mandatory field
(cherry picked from commit 8c34cc0e00)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card/job_card.json
2023-05-23 05:20:21 +00:00
vishnu
d6427cfe53 fix: error while saving job card
(cherry picked from commit a209fb4b64)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card/job_card.json
2023-05-23 05:20:21 +00:00
rohitwaghchaure
774092343a Merge pull request #35374 from frappe/mergify/bp/version-13-hotfix/pr-35227
fix: non manufacturing items/fixed asset items in BOM (backport #35227)
2023-05-22 13:48:10 +05:30
rohitwaghchaure
2aa7729243 fix: get_query filters 2023-05-22 13:16:06 +05:30
Rohit Waghchaure
66ba74f3fc fix: non manufacturing items/fixed asset items in BOM
(cherry picked from commit aba8431d70)
2023-05-22 07:43:34 +00:00
JunKangChin
dc04b24234 fix: Incorrect Earned Leaves Proration (#35156) 2023-05-21 21:36:10 +05:30
mergify[bot]
eb243c2470 fix: allow over-payment against SO (#35079)
fix: allow over-payment against SO (#35079)

(cherry picked from commit 870b02b03c)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-05-20 21:03:59 +05:30
ruthra kumar
f7ed4ecd56 Merge pull request #35299 from ruthra-kumar/replace_join_with_subquery
refactor: replace join with subquery in PCV
2023-05-19 11:00:42 +05:30
ruthra kumar
9af4e117d4 Merge pull request #35354 from frappe/mergify/bp/version-13-hotfix/pr-35335
fix: tds incorrectly calculated for invoice that are below threshold (backport #35335)
2023-05-18 14:36:28 +05:30
ruthra kumar
6191cfee4c Merge pull request #35356 from frappe/mergify/bp/version-13-hotfix/pr-35142
fix: ineffective bypass flag for Credit Limit in Customer Group (backport #35142)
2023-05-18 13:31:14 +05:30
ruthra kumar
f0c9d89aab fix: bypass flag in Customer Group wasn't effective
(cherry picked from commit f9a4972cb6)
2023-05-18 07:32:36 +00:00
ruthra kumar
06deecbd92 fix(test): cumulative threshold checks
(cherry picked from commit 132846bbd1)
2023-05-18 07:09:19 +00:00
ruthra kumar
6c170abdf9 fix: tds incorrectly calculated for invoice that are below threshold
Two purchase invoices for the same supplier, using different tax
withholding categories have this issue.

| Category | single | cumulative |
|----------+--------+------------|
| cat1     |    100 |        500 |
| cat2     |   1000 |       5000 |

1. PINV1 of net total: 105/- uses cat1. TDS is calculated as it
breached single threshold
2. PINV2 of net total: 200/- uses cat2. TDS incorrectly calculated as
PINV1 already has TDS calculated and 'consider_party_ledger_amount' is enabled.

(cherry picked from commit 84b7c1bba0)
2023-05-18 07:09:19 +00:00
mergify[bot]
7506132861 fix: depreciation schedule for existing assets [v14] (backport #35255) (#35347)
* fix: depreciation schedule for existing assets [v14] (#35255)

* fix: depreciation schedule for existing assets

* chore: correct logic for existing assets and fix test

(cherry picked from commit 0a080efce2)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.py

* chore: fix conflict

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-05-17 22:52:13 +05:30
mergify[bot]
387f8b9e1a fix: consider 0 if rate/qty are null (backport #35338) (#35341)
fix: consider 0 if rate/qty are null (#35338)

[skip ci]

(cherry picked from commit e5c86bc2e8)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-05-17 16:17:25 +05:30
ruthra kumar
e6a9252f79 refactor: replace join with sub-query for fetching accounts 2023-05-14 16:54:56 +05:30
29 changed files with 323 additions and 126 deletions

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = "13.50.5"
__version__ = "13.51.1"
def get_default_company(user=None):

View File

@@ -169,21 +169,18 @@ class PeriodClosingVoucher(AccountsController):
return frappe.db.sql(
"""
select
t2.account_currency,
t1.account_currency,
{dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
from `tabGL Entry` t1
where
t1.is_cancelled = 0
and t1.account = t2.name
and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2
and t2.company = %s
and t1.account in (select name from `tabAccount` where report_type = 'Profit and Loss' and docstatus < 2 and company = %s)
and t1.posting_date between %s and %s
group by {dimension_fields}
""".format(
dimension_fields=", ".join(dimension_fields)
dimension_fields=", ".join(dimension_fields),
),
(self.company, self.get("year_start_date"), self.posting_date),
as_dict=1,

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
from six import iteritems
@@ -675,18 +676,22 @@ def get_bin_qty(item_code, warehouse):
def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql(
"""select sum(p_item.qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent
and ifnull(p.consolidated_invoice, '') = ''
and p_item.docstatus = 1
and p_item.item_code = %s
and p_item.warehouse = %s
""",
(item_code, warehouse),
as_dict=1,
)
p_inv = frappe.qb.DocType("POS Invoice")
p_item = frappe.qb.DocType("POS Invoice Item")
reserved_qty = (
frappe.qb.from_(p_inv)
.from_(p_item)
.select(Sum(p_item.qty).as_("qty"))
.where(
(p_inv.name == p_item.parent)
& (IfNull(p_inv.consolidated_invoice, "") == "")
& (p_inv.is_return == 0)
& (p_item.docstatus == 1)
& (p_item.item_code == item_code)
& (p_item.warehouse == warehouse)
)
).run(as_dict=True)
return reserved_qty[0].qty or 0 if reserved_qty else 0

View File

@@ -1107,7 +1107,7 @@ class SalesInvoice(SellingController):
if self.is_return:
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
asset, item.base_net_amount, item.finance_book
asset, item.base_net_amount, item.finance_book, self.posting_date
)
asset.db_set("disposal_date", None)
@@ -1122,7 +1122,7 @@ class SalesInvoice(SellingController):
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.base_net_amount, item.finance_book
asset, item.base_net_amount, item.finance_book, self.posting_date
)
asset.db_set("disposal_date", self.posting_date)

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from collections import OrderedDict
import frappe
from frappe import _, qb, scrub
@@ -666,7 +667,7 @@ class GrossProfitGenerator(object):
def load_invoice_items(self):
conditions = ""
if self.filters.company:
conditions += " and company = %(company)s"
conditions += " and `tabSales Invoice`.company = %(company)s"
if self.filters.from_date:
conditions += " and posting_date >= %(from_date)s"
if self.filters.to_date:
@@ -760,30 +761,30 @@ class GrossProfitGenerator(object):
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
"""
parents = []
grouped = OrderedDict()
for row in self.si_list:
if row.parent not in parents:
parents.append(row.parent)
# initialize list with a header row for each new parent
grouped.setdefault(row.parent, [self.get_invoice_row(row)]).append(
row.update(
{"indent": 1.0, "parent_invoice": row.parent, "invoice_or_item": row.item_code}
) # descendant rows will have indent: 1.0 or greater
)
parents_index = 0
for index, row in enumerate(self.si_list):
if parents_index < len(parents) and row.parent == parents[parents_index]:
invoice = self.get_invoice_row(row)
self.si_list.insert(index, invoice)
parents_index += 1
# if item is a bundle, add it's components as seperate rows
if frappe.db.exists("Product Bundle", row.item_code):
bundled_items = self.get_bundle_items(row)
for x in bundled_items:
bundle_item = self.get_bundle_item_row(row, x)
grouped.get(row.parent).append(bundle_item)
else:
# skipping the bundle items rows
if not row.indent:
row.indent = 1.0
row.parent_invoice = row.parent
row.invoice_or_item = row.item_code
self.si_list.clear()
if frappe.db.exists("Product Bundle", row.item_code):
self.add_bundle_items(row, index)
for items in grouped.values():
self.si_list.extend(items)
def get_invoice_row(self, row):
# header row format
return frappe._dict(
{
"parent_invoice": "",
@@ -812,13 +813,6 @@ class GrossProfitGenerator(object):
}
)
def add_bundle_items(self, product_bundle, index):
bundle_items = self.get_bundle_items(product_bundle)
for i, item in enumerate(bundle_items):
bundle_item = self.get_bundle_item_row(product_bundle, item)
self.si_list.insert((index + i + 1), bundle_item)
def get_bundle_items(self, product_bundle):
return frappe.get_all(
"Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]

View File

@@ -339,13 +339,9 @@ class Asset(AccountsController):
if should_get_last_day:
schedule_date = get_last_day(schedule_date)
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_disposal:
from_date = self.get_from_date(finance_book.finance_book)
from_date = self.get_from_date_for_disposal(finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book,
depreciation_amount,
@@ -368,14 +364,20 @@ class Asset(AccountsController):
break
# For first row
if (
(has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation
and n == 0
):
from_date = add_days(
self.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
if n == 0 and has_pro_rata and not self.opening_accumulated_depreciation:
from_date = add_days(self.available_for_use_date, -1)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book,
depreciation_amount,
from_date,
finance_book.depreciation_start_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
from_date = add_months(
getdate(self.available_for_use_date),
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book,
depreciation_amount,
@@ -383,10 +385,6 @@ class Asset(AccountsController):
finance_book.depreciation_start_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
@@ -411,9 +409,7 @@ class Asset(AccountsController):
depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount:
continue
@@ -432,7 +428,7 @@ class Asset(AccountsController):
)
skip_row = True
if depreciation_amount > 0:
if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0:
self.append(
"schedules",
{
@@ -490,16 +486,19 @@ class Asset(AccountsController):
for idx, s in enumerate(self.schedules, 1):
s.idx = idx
def get_from_date(self, finance_book):
def get_from_date_for_disposal(self, finance_book):
if not self.get("schedules"):
return self.available_for_use_date
return add_months(
getdate(self.available_for_use_date),
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
)
if len(self.finance_books) == 1:
return self.schedules[-1].schedule_date
from_date = ""
for schedule in self.get("schedules"):
if schedule.finance_book == finance_book:
if schedule.finance_book == finance_book.finance_book:
from_date = schedule.schedule_date
if from_date:
@@ -1281,9 +1280,11 @@ def get_straight_line_or_manual_depr_amount(asset, row):
)
# if the Depreciation Schedule is being prepared for the first time
else:
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
return (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
def get_wdv_or_dd_depr_amount(

View File

@@ -279,7 +279,7 @@ def scrap_asset(asset_name):
je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name)
for entry in get_gl_entries_on_asset_disposal(asset):
for entry in get_gl_entries_on_asset_disposal(asset, date):
entry.update({"reference_type": "Asset", "reference_name": asset_name})
je.append("accounts", entry)
@@ -403,7 +403,10 @@ def disposal_happens_in_the_future(posting_date_of_disposal):
return False
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None, date=None):
if not date:
date = getdate()
(
fixed_asset_account,
asset,
@@ -420,23 +423,30 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
"debit_in_account_currency": asset.gross_purchase_amount,
"debit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
{
"account": accumulated_depr_account,
"credit_in_account_currency": accumulated_depr_amount,
"credit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
]
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
if profit_amount:
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
get_profit_gl_entries(
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
)
return gl_entries
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None, date=None):
if not date:
date = getdate()
(
fixed_asset_account,
asset,
@@ -453,18 +463,22 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
"credit_in_account_currency": asset.gross_purchase_amount,
"credit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
]
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
if profit_amount:
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
get_profit_gl_entries(
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
)
return gl_entries
@@ -491,7 +505,12 @@ def get_asset_details(asset, finance_book=None):
)
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
def get_profit_gl_entries(
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date=None
):
if not date:
date = getdate()
debit_or_credit = "debit" if profit_amount < 0 else "credit"
gl_entries.append(
{
@@ -499,6 +518,7 @@ def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciat
"cost_center": depreciation_cost_center,
debit_or_credit: abs(profit_amount),
debit_or_credit + "_in_account_currency": abs(profit_amount),
"posting_date": date,
}
)

View File

@@ -298,6 +298,79 @@ class TestAsset(AssetSetup):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_gle_made_by_asset_sale_for_existing_asset(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-04-01",
purchase_date="2020-04-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=5,
number_of_depreciations_booked=2,
frequency_of_depreciation=12,
depreciation_start_date="2023-03-31",
opening_accumulated_depreciation=24000,
gross_purchase_amount=60000,
submit=1,
)
expected_depr_values = [
["2023-03-31", 12000, 36000],
["2024-03-31", 12000, 48000],
["2025-03-31", 12000, 60000],
]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_depr_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_depr_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_depr_values[i][2], schedule.accumulated_depreciation_amount)
post_depreciation_entries(date="2023-03-31")
si = create_sales_invoice(
item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23")
)
asset.load_from_db()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertTrue(schedule.journal_entry)
expected_gle = (
(
"_Test Accumulated Depreciations - _TC",
37742.47,
0.0,
),
(
"_Test Fixed Asset - _TC",
0.0,
60000.0,
),
(
"_Test Gain/Loss on Asset Disposal - _TC",
0.0,
17742.47,
),
("Debtors - _TC", 40000.0, 0.0),
)
gle = frappe.db.sql(
"""select account, debit, credit from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no = %s
order by account""",
si.name,
)
self.assertSequenceEqual(gle, expected_gle)
def test_asset_with_maintenance_required_status_after_sale(self):
asset = create_asset(
calculate_depreciation=1,
@@ -569,7 +642,7 @@ class TestDepreciationMethods(AssetSetup):
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]]
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules")

View File

@@ -96,7 +96,6 @@ class AssetCategory(Document):
frappe.throw(msg, title=_("Missing Account"))
@frappe.whitelist()
def get_asset_category_account(
fieldname, item=None, asset=None, account=None, asset_category=None, company=None
):

View File

@@ -678,7 +678,7 @@ class StockController(AccountsController):
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
return message
def repost_future_sle_and_gle(self):
def repost_future_sle_and_gle(self, force=False):
args = frappe._dict(
{
"posting_date": self.posting_date,
@@ -689,7 +689,10 @@ class StockController(AccountsController):
}
)
if future_sle_exists(args) or repost_required_for_queue(self):
if self.docstatus == 2:
force = True
if force or future_sle_exists(args) or repost_required_for_queue(self):
item_based_reposting = cint(
frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
)

View File

@@ -192,9 +192,9 @@ def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj
date = getdate(frappe.flags.current_date) or getdate()
if based_on_doj:
# if leave type allocation is based on DOJ, and the date of assignment creation is same as DOJ,
# if leave type allocation is based on DOJ, and the date of assignment creation is after DOJ,
# then the month should be considered
if date.day == date_of_joining.day:
if date.day >= date_of_joining.day:
months_passed += 1
else:
last_day_of_month = get_last_day(date)

View File

@@ -160,4 +160,3 @@ class TestLoanDisbursement(unittest.TestCase):
interest = per_day_interest * 15
self.assertEqual(amounts["pending_principal_amount"], 1500000)
self.assertEqual(amounts["interest_amount"], flt(interest + previous_interest, 2))

View File

@@ -22,7 +22,7 @@ class LoanInterestAccrual(AccountsController):
frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
if not self.last_accrual_date:
self.last_accrual_date = get_last_accrual_date(self.loan)
self.last_accrual_date = get_last_accrual_date(self.loan, self.posting_date)
def on_submit(self):
self.make_gl_entries()
@@ -274,14 +274,14 @@ def make_loan_interest_accrual_entry(args):
def get_no_of_days_for_interest_accural(loan, posting_date):
last_interest_accrual_date = get_last_accrual_date(loan.name)
last_interest_accrual_date = get_last_accrual_date(loan.name, posting_date)
no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1
return no_of_days
def get_last_accrual_date(loan):
def get_last_accrual_date(loan, posting_date):
last_posting_date = frappe.db.sql(
""" SELECT MAX(posting_date) from `tabLoan Interest Accrual`
WHERE loan = %s and docstatus = 1""",
@@ -289,12 +289,30 @@ def get_last_accrual_date(loan):
)
if last_posting_date[0][0]:
last_interest_accrual_date = last_posting_date[0][0]
# interest for last interest accrual date is already booked, so add 1 day
return add_days(last_posting_date[0][0], 1)
last_disbursement_date = get_last_disbursement_date(loan, posting_date)
if last_disbursement_date and getdate(last_disbursement_date) > getdate(
last_interest_accrual_date
):
last_interest_accrual_date = last_disbursement_date
return add_days(last_interest_accrual_date, 1)
else:
return frappe.db.get_value("Loan", loan, "disbursement_date")
def get_last_disbursement_date(loan, posting_date):
last_disbursement_date = frappe.db.get_value(
"Loan Disbursement",
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)},
"MAX(posting_date)",
)
return last_disbursement_date
def days_in_year(year):
days = 365

View File

@@ -102,7 +102,7 @@ class LoanRepayment(AccountsController):
if flt(self.total_interest_paid, precision) > flt(self.interest_payable, precision):
if not self.is_term_loan:
# get last loan interest accrual date
last_accrual_date = get_last_accrual_date(self.against_loan)
last_accrual_date = get_last_accrual_date(self.against_loan, self.posting_date)
# get posting date upto which interest has to be accrued
per_day_interest = get_per_day_interest(
@@ -724,7 +724,7 @@ def get_amounts(amounts, against_loan, posting_date):
if due_date:
pending_days = date_diff(posting_date, due_date) + 1
else:
last_accrual_date = get_last_accrual_date(against_loan_doc.name)
last_accrual_date = get_last_accrual_date(against_loan_doc.name, posting_date)
pending_days = date_diff(posting_date, last_accrual_date) + 1
if pending_days > 0:

View File

@@ -48,7 +48,8 @@ frappe.ui.form.on("BOM", {
return {
query: "erpnext.manufacturing.doctype.bom.bom.item_query",
filters: {
"item_code": doc.item
"include_item_in_manufacturing": 1,
"is_fixed_asset": 0
}
};
});

View File

@@ -1346,8 +1346,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
if not has_variants:
query_filters["has_variants"] = 0
if filters and filters.get("is_stock_item"):
query_filters["is_stock_item"] = 1
if filters:
for fieldname, value in filters.items():
query_filters[fieldname] = value
return frappe.get_list(
"Item",

View File

@@ -605,6 +605,45 @@ class TestBOM(FrappeTestCase):
bom.update_cost()
self.assertFalse(bom.flags.cost_updated)
def test_do_not_include_manufacturing_and_fixed_items(self):
from erpnext.manufacturing.doctype.bom.bom import item_query
if not frappe.db.exists("Asset Category", "Computers-Test"):
doc = frappe.get_doc({"doctype": "Asset Category", "asset_category_name": "Computers-Test"})
doc.flags.ignore_mandatory = True
doc.insert()
for item_code, properties in {
"_Test RM Item 1 Do Not Include In Manufacture": {
"is_stock_item": 1,
"include_item_in_manufacturing": 0,
},
"_Test RM Item 2 Fixed Asset Item": {
"is_fixed_asset": 1,
"is_stock_item": 0,
"asset_category": "Computers-Test",
},
"_Test RM Item 3 Manufacture Item": {"is_stock_item": 1, "include_item_in_manufacturing": 1},
}.items():
make_item(item_code, properties)
data = item_query(
"Item",
txt="_Test RM Item",
searchfield="name",
start=0,
page_len=20000,
filters={"include_item_in_manufacturing": 1, "is_fixed_asset": 0},
)
items = []
for row in data:
items.append(row[0])
self.assertTrue("_Test RM Item 1 Do Not Include In Manufacture" not in items)
self.assertTrue("_Test RM Item 2 Fixed Asset Item" not in items)
self.assertTrue("_Test RM Item 3 Manufacture Item" in items)
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@@ -420,7 +420,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2021-11-12 10:15:06.572401",
"modified": "2023-05-22 23:26:57.589331",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",

View File

@@ -657,7 +657,7 @@ class JobCard(Document):
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
if self.docstatus < 2:
if self.for_quantity <= self.transferred_qty:
if flt(self.for_quantity) <= flt(self.transferred_qty):
self.status = "Material Transferred"
if self.time_logs:

View File

@@ -7,4 +7,6 @@ def execute():
frappe.reload_doc("manufacturing", "doctype", "work_order")
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
frappe.db.sql(
"""UPDATE `tabWork Order Item` SET amount = ifnull(rate, 0.0) * ifnull(required_qty, 0.0)"""
)

View File

@@ -1,17 +1,18 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import datetime
import math
from datetime import date
import frappe
from frappe import _, msgprint
from frappe.model.naming import make_autoname
from frappe.utils import (
add_days,
ceil,
cint,
cstr,
date_diff,
floor,
flt,
formatdate,
get_first_day,
@@ -57,8 +58,10 @@ class SalarySlip(TransactionBase):
"float": float,
"long": int,
"round": round,
"date": datetime.date,
"date": date,
"getdate": getdate,
"ceil": ceil,
"floor": floor,
}
def autoname(self):
@@ -959,7 +962,7 @@ class SalarySlip(TransactionBase):
tax_slab.allow_tax_exemption, payroll_period=payroll_period
)
future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * (
math.ceil(remaining_sub_periods) - 1
ceil(remaining_sub_periods) - 1
)
# get taxable_earnings, addition_earnings for current actual payment days

View File

@@ -183,13 +183,6 @@ def get_columns(earning_types, ded_types):
"fieldtype": "Float",
"width": 120,
},
{
"label": _("Currency"),
"fieldname": "currency",
"fieldtype": "Link",
"options": "Currency",
"hidden": 1,
},
]
for earning in earning_types:
@@ -247,6 +240,13 @@ def get_columns(earning_types, ded_types):
"options": "currency",
"width": 120,
},
{
"label": _("Currency"),
"fieldname": "currency",
"fieldtype": "Data",
"options": "Currency",
"hidden": 1,
},
]
)

View File

@@ -25,20 +25,38 @@ frappe.listview_settings['Task'] = {
}
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
},
gantt_custom_popup_html: function(ganttobj, task) {
var html = `<h5><a style="text-decoration:underline"\
href="/app/task/${ganttobj.id}""> ${ganttobj.name} </a></h5>`;
gantt_custom_popup_html: function (ganttobj, task) {
let html = `
<a class="text-white mb-2 inline-block cursor-pointer"
href="/app/task/${ganttobj.id}"">
${ganttobj.name}
</a>
`;
if(task.project) html += `<p>Project: ${task.project}</p>`;
html += `<p>Progress: ${ganttobj.progress}</p>`;
if (task.project) {
html += `<p class="mb-1">${__("Project")}:
<a class="text-white inline-block"
href="/app/project/${task.project}"">
${task.project}
</a>
</p>`;
}
html += `<p class="mb-1">
${__("Progress")}:
<span class="text-white">${ganttobj.progress}%</span>
</p>`;
if(task._assign_list) {
html += task._assign_list.reduce(
(html, user) => html + frappe.avatar(user)
, '');
if (task._assign) {
const assign_list = JSON.parse(task._assign);
const assignment_wrapper = `
<span>Assigned to:</span>
<span class="text-white">
${assign_list.map((user) => frappe.user_info(user).fullname).join(", ")}
</span>
`;
html += assignment_wrapper;
}
return html;
}
return `<div class="p-3" style="min-width: 220px">${html}</div>`;
},
};

View File

@@ -656,11 +656,15 @@ def get_credit_limit(customer, company):
if not credit_limit:
customer_group = frappe.get_cached_value("Customer", customer, "customer_group")
credit_limit = frappe.db.get_value(
result = frappe.db.get_values(
"Customer Credit Limit",
{"parent": customer_group, "parenttype": "Customer Group", "company": company},
"credit_limit",
fieldname=["credit_limit", "bypass_credit_limit_check"],
as_dict=True,
)
if result and not result[0].bypass_credit_limit_check:
credit_limit = result[0].credit_limit
if not credit_limit:
credit_limit = frappe.get_cached_value("Company", company, "credit_limit")

View File

@@ -235,7 +235,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
}
}
// payment request
if(flt(doc.per_billed)<100) {
if(flt(doc.per_billed, precision('per_billed', doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance) {
this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create'));
this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create'));
}

View File

@@ -22,6 +22,10 @@ def boot_session(bootinfo):
bootinfo.sysdefaults.allow_stale = cint(
frappe.db.get_single_value("Accounts Settings", "allow_stale")
)
bootinfo.sysdefaults.over_billing_allowance = frappe.db.get_single_value(
"Accounts Settings", "over_billing_allowance"
)
bootinfo.sysdefaults.quotation_valid_till = cint(
frappe.db.get_single_value("Selling Settings", "default_valid_till")
)

View File

@@ -292,7 +292,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
)
while remaining_stock_qty > 0 and available_locations:
while flt(remaining_stock_qty) > 0 and available_locations:
item_location = available_locations.pop(0)
item_location = frappe._dict(item_location)

View File

@@ -294,3 +294,19 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
self.assertRaises(frappe.ValidationError, riv.save)
accounts_settings.acc_frozen_upto = ""
accounts_settings.save()
def test_create_repost_entry_for_cancelled_document(self):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
get_multiple_items=True,
)
self.assertTrue(pr.docstatus == 1)
self.assertFalse(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name}))
pr.load_from_db()
pr.cancel()
self.assertTrue(pr.docstatus == 2)
self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name}))

View File

@@ -2271,7 +2271,7 @@ def move_sample_to_retention_warehouse(company, items):
"basic_rate": item.get("valuation_rate"),
"uom": item.get("uom"),
"stock_uom": item.get("stock_uom"),
"conversion_factor": 1.0,
"conversion_factor": item.get("conversion_factor") or 1.0,
"serial_no": sample_serial_nos,
"batch_no": item.get("batch_no"),
},