Compare commits

..

54 Commits

Author SHA1 Message Date
Frappe PR Bot
756ac6c587 chore(release): Bumped to Version 14.24.3
## [14.24.3](https://github.com/frappe/erpnext/compare/v14.24.2...v14.24.3) (2023-05-18)

### Bug Fixes

* tds incorrectly calculated for invoice that are below threshold ([14565ed](14565ed8b1))
* **test:** cumulative threshold checks ([cdd378c](cdd378c518))
2023-05-18 14:07:04 +00:00
ruthra kumar
b5b1b2da32 Merge pull request #35359 from frappe/mergify/bp/version-14/pr-35335
fix: tds incorrectly calculated for invoice that are below threshold (backport #35335)
2023-05-18 19:35:11 +05:30
ruthra kumar
cdd378c518 fix(test): cumulative threshold checks
(cherry picked from commit 132846bbd1)
2023-05-18 13:30:03 +00:00
ruthra kumar
14565ed8b1 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 13:30:02 +00:00
Frappe PR Bot
7076c23a4f chore(release): Bumped to Version 14.24.2
## [14.24.2](https://github.com/frappe/erpnext/compare/v14.24.1...v14.24.2) (2023-05-16)

### Bug Fixes

* add missing options for `Content Align` ([3697e8f](3697e8f1f9))
* allow over-payment against SO ([#35079](https://github.com/frappe/erpnext/issues/35079)) ([fe9e0c2](fe9e0c2121))
* bad strings format for command get-untraslated  ([#34361](https://github.com/frappe/erpnext/issues/34361)) ([5a54296](5a54296686))
* bad strings format for update-translations ([#34592](https://github.com/frappe/erpnext/issues/34592)) ([e3c1d73](e3c1d736ce))
* bom item filter issue ([098603d](098603dd35))
* cancelled vouchers in tax withheld vouchers list ([#35309](https://github.com/frappe/erpnext/issues/35309)) ([c41e1d7](c41e1d7d71))
* enqueue submit/cancel action for stock entry to avoid time out error ([457846e](457846e34c))
* force to do reposting for cancelled document ([6f96e5d](6f96e5dcd4))
* function `batch_no` should only be declared once ([#35115](https://github.com/frappe/erpnext/issues/35115)) ([26928b3](26928b395b))
* incorrect packing items ([ab56470](ab56470171))
* inventory dimension for inter company transfer return use case ([6d121b8](6d121b8107))
* inventory dimension for material transfer not working ([1d8050d](1d8050d24d))
* item list view not working ([d9efa66](d9efa662d4))
* port option for additional_conditions in item wise sales register ([#35187](https://github.com/frappe/erpnext/issues/35187)) ([42037f9](42037f9f73))
* recalculate costs in SCR while reposting ([9a8ee62](9a8ee62d5a))
* sales person allocated amount calculation error nonetype and float ([#35293](https://github.com/frappe/erpnext/issues/35293)) ([3a7c69f](3a7c69fc71))
* test case ([3f8928b](3f8928be5c))
* test case ([9b2b467](9b2b46737e))
* typo ([2772a91](2772a911ed))
* unable to create partial invoice with auto fetch terms enabled ([#35285](https://github.com/frappe/erpnext/issues/35285)) ([fa9fa97](fa9fa97e05))
* update workstation hour rate when workstation change in job card ([bc88415](bc88415e73))
* **UX:** misc "home" onboarding improvements (backport [#35319](https://github.com/frappe/erpnext/issues/35319)) ([#35321](https://github.com/frappe/erpnext/issues/35321)) ([f8c58b6](f8c58b6893))
* validate for active sla ([#32132](https://github.com/frappe/erpnext/issues/32132)) ([38e27a6](38e27a68d5))

### Performance Improvements

* cache and simplify queries for holiday list (backport [#35315](https://github.com/frappe/erpnext/issues/35315)) ([#35318](https://github.com/frappe/erpnext/issues/35318)) ([0e78403](0e7840301f))
2023-05-16 17:00:00 +00:00
Deepesh Garg
bdfd682664 Merge pull request #35324 from frappe/version-14-hotfix
chore: release v14
2023-05-16 22:28:33 +05:30
mergify[bot]
fa9fa97e05 fix: unable to create partial invoice with auto fetch terms enabled (#35285)
fix: unable to create partial invoice with auto fetch terms enabled (#35285)

fix: fetch so/po terms if auto fetch is enabled
(cherry picked from commit 0da6c1688b)

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2023-05-16 18:59:12 +05:30
mergify[bot]
c41e1d7d71 fix: cancelled vouchers in tax withheld vouchers list (#35309)
fix: cancelled vouchers in tax withheld vouchers list (#35309)

(cherry picked from commit 776a83066d)

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2023-05-16 18:58:04 +05:30
rohitwaghchaure
8f8c0a597b Merge pull request #35329 from frappe/mergify/bp/version-14-hotfix/pr-35328
fix: force to do reposting for cancelled document (backport #35328)
2023-05-16 18:53:33 +05:30
Rohit Waghchaure
6f96e5dcd4 fix: force to do reposting for cancelled document
(cherry picked from commit 6e661e7c0e)
2023-05-16 12:03:43 +00:00
mergify[bot]
f8c58b6893 fix(UX): misc "home" onboarding improvements (backport #35319) (#35321)
fix(UX): misc "home" onboarding improvements (#35319)

* fix(UX): cleanup "Home" onboarding

- Remove company, letterhead, data import etc from home onboarding step

* fix(UX): Show quick entry for item

* chore: fix copy here and there

(cherry picked from commit 5574d9a72d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-05-16 16:00:25 +05:30
mergify[bot]
0e7840301f perf: cache and simplify queries for holiday list (backport #35315) (#35318)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2023-05-16 13:44:35 +05:30
rohitwaghchaure
19f08afcef Merge pull request #35314 from frappe/mergify/bp/version-14-hotfix/pr-35312
fix: item list view not working (backport #35312)
2023-05-16 11:10:18 +05:30
mergify[bot]
42037f9f73 fix: port option for additional_conditions in item wise sales register (#35187)
fix: port option for additional_conditions in item wise sales register (#35187)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 2a609616d9)

Co-authored-by: Smit Vora <smitvora203@gmail.com>
2023-05-16 07:29:48 +05:30
Rohit Waghchaure
d9efa662d4 fix: item list view not working
(cherry picked from commit 0489e30244)
2023-05-15 19:40:23 +00:00
rohitwaghchaure
ee147e62d5 Merge pull request #35311 from CodeVenturers/version-14-hotfix
fix: update workstation hour rate when workstation change in job card
2023-05-16 00:44:26 +05:30
vishnu
bc88415e73 fix: update workstation hour rate when workstation change in job card 2023-05-15 14:50:40 +00:00
Sagar Sharma
5463f0b137 Merge pull request #35308 from frappe/mergify/bp/version-14-hotfix/pr-35138
fix: recalculate costs in SCR while reposting (backport #35138)
2023-05-15 17:14:57 +05:30
Sagar Sharma
a8fc17e0ae refactor: use calculate_items_qty_and_amount() to update scr items rate
(cherry picked from commit 9c72c2a6cb)
2023-05-15 10:56:51 +00:00
Sagar Sharma
0575b105d0 refactor(minor): rename function to be more descriptive
(cherry picked from commit d6433f803b)
2023-05-15 10:56:51 +00:00
Sagar Sharma
4c8dbeddec test: add test case
(cherry picked from commit e0b22edb2e)
2023-05-15 10:56:51 +00:00
s-aga-r
9a8ee62d5a fix: recalculate costs in SCR while reposting
(cherry picked from commit a6cb6c6f47)
2023-05-15 10:56:50 +00:00
mergify[bot]
3a7c69fc71 fix: sales person allocated amount calculation error nonetype and float (#35293)
fix: sales person allocated amount calculation error nonetype and float (#35293)

fix: sales person allocated amount calculation error nontype and float
(cherry picked from commit 0c8276ec82)

Co-authored-by: Indrajith.vs <91895505+Gubbu77@users.noreply.github.com>
2023-05-15 13:16:47 +05:30
rohitwaghchaure
dd116a3071 Merge pull request #35291 from frappe/mergify/bp/version-14-hotfix/pr-35289
fix: inventory dimension for returned inter company transfer (backport #35289)
2023-05-14 16:29:34 +05:30
rohitwaghchaure
3f8928be5c fix: test case 2023-05-14 15:49:58 +05:30
mergify[bot]
26928b395b fix: function batch_no should only be declared once (#35115)
fix: function `batch_no` should only be declared once (#35115)

fix: remove twice event call of `batch_no` to update batch qty
(cherry picked from commit 19cd687784)

Co-authored-by: Daizy Modi <modidaizy5217@gmail.com>
2023-05-14 11:54:15 +05:30
mergify[bot]
fe9e0c2121 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>
2023-05-14 11:44:50 +05:30
Deepesh Garg
77cf2e5475 Merge pull request #35262 from akiratfli/fix-bad-string
fix: bad strings format for translations
2023-05-14 08:06:24 +05:30
rohitwaghchaure
2772a911ed fix: typo 2023-05-13 15:14:41 +05:30
Rohit Waghchaure
6d121b8107 fix: inventory dimension for inter company transfer return use case
(cherry picked from commit 38aaba5720)
2023-05-13 08:57:03 +00:00
Sagar Sharma
c30dda3328 Merge pull request #35283 from frappe/mergify/bp/version-14-hotfix/pr-35275
fix: add missing options for `Content Align` (backport #35275)
2023-05-13 09:42:20 +05:30
Sagar Sharma
b2e6fdc0cb Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-35275 2023-05-13 09:41:30 +05:30
rohitwaghchaure
75af689f77 Merge pull request #35282 from frappe/mergify/bp/version-14-hotfix/pr-35224
fix: inventory dimension for material transfer not working (backport #35224)
2023-05-13 09:36:42 +05:30
Sagar Sharma
3697e8f1f9 fix: add missing options for Content Align
(cherry picked from commit d16caa2d2c)
2023-05-13 04:05:15 +00:00
Rohit Waghchaure
1d8050d24d fix: inventory dimension for material transfer not working
(cherry picked from commit 6798b900ef)
2023-05-12 20:46:04 +00:00
Frappe PR Bot
897a467846 chore(release): Bumped to Version 14.24.1
## [14.24.1](https://github.com/frappe/erpnext/compare/v14.24.0...v14.24.1) (2023-05-12)

### Bug Fixes

* bom item filter issue ([54e822d](54e822d83a))
* enqueue submit/cancel action for stock entry to avoid time out error ([762a46a](762a46a5e3))
* incorrect packing items ([b02ed0d](b02ed0d9a8))
* test case ([1783594](1783594178))
2023-05-12 10:05:31 +00:00
rohitwaghchaure
f06a8fd01c Merge pull request #35270 from frappe/mergify/bp/version-14/pr-35266
fix: enqueue submit/cancel action for stock entry to avoid time out error (backport #35261) (backport #35266)
2023-05-12 15:30:55 +05:30
rohitwaghchaure
b139ec6ec0 Merge pull request #35271 from frappe/mergify/bp/version-14/pr-35269
fix: BOM item filter issue (backport #35268) (backport #35269)
2023-05-12 15:30:43 +05:30
rohitwaghchaure
e97eaccdfb Merge pull request #35276 from frappe/mergify/bp/version-14/pr-35274
fix: incorrect packing items (backport #35273) (backport #35274)
2023-05-12 15:30:28 +05:30
Rohit Waghchaure
b02ed0d9a8 fix: incorrect packing items
(cherry picked from commit a686b8c337)
(cherry picked from commit ab56470171)
2023-05-12 09:37:12 +00:00
rohitwaghchaure
4206f01c05 Merge pull request #35274 from frappe/mergify/bp/version-14-hotfix/pr-35273
fix: incorrect packing items (backport #35273)
2023-05-12 15:06:25 +05:30
Rohit Waghchaure
ab56470171 fix: incorrect packing items
(cherry picked from commit a686b8c337)
2023-05-12 09:13:52 +00:00
Rohit Waghchaure
54e822d83a fix: bom item filter issue
(cherry picked from commit 2879cb7c28)
(cherry picked from commit 098603dd35)
2023-05-12 07:42:57 +00:00
rohitwaghchaure
8af1e49e96 Merge pull request #35269 from frappe/mergify/bp/version-14-hotfix/pr-35268
fix: BOM item filter issue (backport #35268)
2023-05-12 13:12:11 +05:30
Rohit Waghchaure
1783594178 fix: test case
(cherry picked from commit 2d6f112727)
(cherry picked from commit 9b2b46737e)
2023-05-12 07:41:34 +00:00
Rohit Waghchaure
762a46a5e3 fix: enqueue submit/cancel action for stock entry to avoid time out error
(cherry picked from commit 7a3801578c)
(cherry picked from commit 457846e34c)
2023-05-12 07:41:34 +00:00
rohitwaghchaure
19f0676592 Merge pull request #35266 from frappe/mergify/bp/version-14-hotfix/pr-35261
fix: enqueue submit/cancel action for stock entry to avoid time out error (backport #35261)
2023-05-12 13:10:48 +05:30
Rohit Waghchaure
098603dd35 fix: bom item filter issue
(cherry picked from commit 2879cb7c28)
2023-05-12 07:40:38 +00:00
Rohit Waghchaure
9b2b46737e fix: test case
(cherry picked from commit 2d6f112727)
2023-05-12 07:03:22 +00:00
Rohit Waghchaure
457846e34c fix: enqueue submit/cancel action for stock entry to avoid time out error
(cherry picked from commit 7a3801578c)
2023-05-12 07:03:21 +00:00
Danny
5a54296686 fix: bad strings format for command get-untraslated (#34361)
(cherry picked from commit ca10e2bb9f)
2023-05-12 03:19:01 +00:00
justin.li
e3c1d736ce fix: bad strings format for update-translations (#34592)
(cherry picked from commit 07c9b99072)
2023-05-12 02:21:19 +00:00
Saqib Ansari
c8e3ce48e1 Merge pull request #35257 from frappe/mergify/bp/version-14-hotfix/pr-32132
fix: validate for active sla (backport #32132)
2023-05-11 17:50:00 +05:30
Shadrak Gurupnor
38e27a68d5 fix: validate for active sla (#32132)
(cherry picked from commit f2b7c9ee66)
2023-05-11 12:18:49 +00:00
42 changed files with 765 additions and 108 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "14.24.0"
__version__ = "14.24.3"
def get_default_company(user=None):

View File

@@ -46,7 +46,7 @@ class BankTransaction(StatusUpdater):
def add_payment_entries(self, vouchers):
"Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
if 0.0 >= self.unallocated_amount:
frappe.throw(frappe._(f"Bank Transaction {self.name} is already fully reconciled"))
frappe.throw(frappe._("Bank Transaction {0} is already fully reconciled").format(self.name))
added = False
for voucher in vouchers:
@@ -114,9 +114,7 @@ class BankTransaction(StatusUpdater):
elif 0.0 > unallocated_amount:
self.db_delete_payment_entry(payment_entry)
frappe.throw(
frappe._(f"Voucher {payment_entry.payment_entry} is over-allocated by {unallocated_amount}")
)
frappe.throw(frappe._("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
self.reload()
@@ -178,7 +176,9 @@ def get_clearance_details(transaction, payment_entry):
if gle["gl_account"] == gl_bank_account:
if gle["amount"] <= 0.0:
frappe.throw(
frappe._(f"Voucher {payment_entry.payment_entry} value is broken: {gle['amount']}")
frappe._("Voucher {0} value is broken: {1}").format(
payment_entry.payment_entry, gle["amount"]
)
)
unmatched_gles -= 1

View File

@@ -303,7 +303,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
apply_tds(frm) {
var me = this;
me.frm.set_value("tax_withheld_vouchers", []);
if (!me.frm.doc.apply_tds) {
me.frm.set_value("tax_withholding_category", '');
me.frm.set_df_property("tax_withholding_category", "hidden", 1);

View File

@@ -302,7 +302,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
"docstatus": 1,
}
if not tax_details.get("consider_party_ledger_amount") and doctype != "Sales Invoice":
if doctype != "Sales Invoice":
filters.update(
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)

View File

@@ -110,9 +110,9 @@ class TestTaxWithholdingCategory(unittest.TestCase):
invoices.append(pi1)
# Cumulative threshold is 30000
# Threshold calculation should be on both the invoices
# TDS should be applied only on 1000
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
# Threshold calculation should be only on the Second invoice
# Second didn't breach, no TDS should be applied
self.assertEqual(pi1.taxes, [])
for d in reversed(invoices):
d.cancel()

View File

@@ -19,14 +19,19 @@ def execute(filters=None):
return _execute(filters)
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
def _execute(
filters=None,
additional_table_columns=None,
additional_query_columns=None,
additional_conditions=None,
):
if not filters:
filters = {}
columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
item_list = get_items(filters, additional_query_columns)
item_list = get_items(filters, additional_query_columns, additional_conditions)
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
@@ -328,7 +333,7 @@ def get_columns(additional_table_columns, filters):
return columns
def get_conditions(filters):
def get_conditions(filters, additional_conditions=None):
conditions = ""
for opts in (
@@ -341,6 +346,9 @@ def get_conditions(filters):
if filters.get(opts[0]):
conditions += opts[1]
if additional_conditions:
conditions += additional_conditions
if filters.get("mode_of_payment"):
conditions += """ and exists(select name from `tabSales Invoice Payment`
where parent=`tabSales Invoice`.name
@@ -376,8 +384,8 @@ def get_group_by_conditions(filters, doctype):
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
def get_items(filters, additional_query_columns):
conditions = get_conditions(filters)
def get_items(filters, additional_query_columns, additional_conditions=None):
conditions = get_conditions(filters, additional_conditions)
if additional_query_columns:
additional_query_columns = ", " + ", ".join(additional_query_columns)

View File

@@ -392,6 +392,9 @@ class AccountsController(TransactionBase):
)
def validate_inter_company_reference(self):
if self.get("is_return"):
return
if self.doctype not in ("Purchase Invoice", "Purchase Receipt"):
return
@@ -1679,6 +1682,9 @@ class AccountsController(TransactionBase):
d.base_payment_amount = flt(
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
)
else:
self.fetch_payment_terms_from_order(po_or_so, doctype)
self.ignore_default_payment_terms_template = 1
def get_order_details(self):
if self.doctype == "Sales Invoice":

View File

@@ -171,7 +171,7 @@ class SellingController(StockController):
self.round_floats_in(sales_person)
sales_person.allocated_amount = flt(
self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0,
flt(self.amount_eligible_for_commission) * sales_person.allocated_percentage / 100.0,
self.precision("allocated_amount", sales_person),
)

View File

@@ -442,7 +442,43 @@ class StockController(AccountsController):
if not dimension:
continue
if row.get(dimension.source_fieldname):
if self.doctype in [
"Purchase Invoice",
"Purchase Receipt",
"Sales Invoice",
"Delivery Note",
"Stock Entry",
]:
if (
(
sl_dict.actual_qty > 0
and not self.get("is_return")
or sl_dict.actual_qty < 0
and self.get("is_return")
)
and self.doctype in ["Purchase Invoice", "Purchase Receipt"]
) or (
(
sl_dict.actual_qty < 0
and not self.get("is_return")
or sl_dict.actual_qty > 0
and self.get("is_return")
)
and self.doctype in ["Sales Invoice", "Delivery Note", "Stock Entry"]
):
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
else:
fieldname_start_with = "to"
if self.doctype in ["Purchase Invoice", "Purchase Receipt"]:
fieldname_start_with = "from"
fieldname = f"{fieldname_start_with}_{dimension.source_fieldname}"
sl_dict[dimension.target_fieldname] = row.get(fieldname)
if not sl_dict.get(dimension.target_fieldname):
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
elif row.get(dimension.source_fieldname):
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
@@ -734,6 +770,9 @@ class StockController(AccountsController):
}
)
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

@@ -741,7 +741,7 @@ class SubcontractingController(StockController):
sco_doc = frappe.get_doc("Subcontracting Order", sco)
sco_doc.update_status()
def set_missing_values_in_additional_costs(self):
def calculate_additional_costs(self):
self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
if self.total_additional_costs:

View File

@@ -36,7 +36,7 @@ class TestSubcontractingController(FrappeTestCase):
sco.remove_empty_rows()
self.assertEqual((len_before - 1), len(sco.service_items))
def test_set_missing_values_in_additional_costs(self):
def test_calculate_additional_costs(self):
sco = get_subcontracting_order(do_not_submit=1)
rate_without_additional_cost = sco.items[0].rate

View File

@@ -165,6 +165,7 @@
"fieldname": "slide_3_content_align",
"fieldtype": "Select",
"label": "Content Align",
"options": "Left\nCentre\nRight",
"reqd": 0
},
{
@@ -214,6 +215,7 @@
"fieldname": "slide_4_content_align",
"fieldtype": "Select",
"label": "Content Align",
"options": "Left\nCentre\nRight",
"reqd": 0
},
{
@@ -263,6 +265,7 @@
"fieldname": "slide_5_content_align",
"fieldtype": "Select",
"label": "Content Align",
"options": "Left\nCentre\nRight",
"reqd": 0
},
{
@@ -274,7 +277,7 @@
}
],
"idx": 2,
"modified": "2021-02-24 15:57:05.889709",
"modified": "2023-05-12 15:03:57.604060",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Hero Slider",

View File

@@ -48,7 +48,6 @@ 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

@@ -562,6 +562,7 @@ class JobCard(Document):
)
def update_work_order_data(self, for_quantity, time_in_mins, wo):
workstation_hour_rate = frappe.get_value("Workstation", self.workstation, "hour_rate")
jc = frappe.qb.DocType("Job Card")
jctl = frappe.qb.DocType("Job Card Time Log")
@@ -587,6 +588,7 @@ class JobCard(Document):
if data.get("workstation") != self.workstation:
# workstations can change in a job card
data.workstation = self.workstation
data.hour_rate = flt(workstation_hour_rate)
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()

View File

@@ -264,7 +264,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
}
}
// 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

@@ -620,6 +620,8 @@ def make_project(source_name, target_doc=None):
@frappe.whitelist()
def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
def set_missing_values(source, target):
target.run_method("set_missing_values")
target.run_method("set_po_nos")
@@ -634,6 +636,8 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
if target.company_address:
target.update(get_fetch_values("Delivery Note", "company_address", target.company_address))
make_packing_list(target)
def update_item(source, target, source_parent):
target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)

View File

@@ -1909,6 +1909,75 @@ class TestSalesOrder(FrappeTestCase):
self.assertEqual(mr.items[0].qty, 6)
def test_packed_items_for_partial_sales_order(self):
# test Update Items with product bundle
for product_bundle in [
"_Test Product Bundle Item Partial 1",
"_Test Product Bundle Item Partial 2",
]:
if not frappe.db.exists("Item", product_bundle):
bundle_item = make_item(product_bundle, {"is_stock_item": 0})
bundle_item.append(
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
)
bundle_item.save(ignore_permissions=True)
for product_bundle in ["_Packed Item Partial 1", "_Packed Item Partial 2"]:
if not frappe.db.exists("Item", product_bundle):
make_item(product_bundle, {"is_stock_item": 1, "stock_uom": "Nos"})
make_stock_entry(item=product_bundle, target="_Test Warehouse - _TC", qty=2, rate=10)
make_product_bundle("_Test Product Bundle Item Partial 1", ["_Packed Item Partial 1"], 1)
make_product_bundle("_Test Product Bundle Item Partial 2", ["_Packed Item Partial 2"], 1)
so = make_sales_order(
item_code="_Test Product Bundle Item Partial 1",
warehouse="_Test Warehouse - _TC",
qty=1,
uom="Nos",
stock_uom="Nos",
conversion_factor=1,
transaction_date=nowdate(),
delivery_note=nowdate(),
do_not_submit=1,
)
so.append(
"items",
{
"item_code": "_Test Product Bundle Item Partial 2",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"uom": "Nos",
"stock_uom": "Nos",
"conversion_factor": 1,
"delivery_note": nowdate(),
},
)
so.save()
so.submit()
dn = make_delivery_note(so.name)
dn.remove(dn.items[1])
dn.save()
dn.submit()
self.assertEqual(len(dn.items), 1)
self.assertEqual(len(dn.packed_items), 1)
self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 1")
so.load_from_db()
dn = make_delivery_note(so.name)
dn.save()
self.assertEqual(len(dn.items), 1)
self.assertEqual(len(dn.packed_items), 1)
self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2")
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")

View File

@@ -299,7 +299,8 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
}
batch_no(doc, cdt, cdn) {
var me = this;
super.batch_no(doc, cdt, cdn);
var item = frappe.get_doc(cdt, cdn);
if (item.serial_no) {
@@ -378,10 +379,6 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
}
}
batch_no(doc, cdt, cdn) {
super.batch_no(doc, cdt, cdn);
}
qty(doc, cdt, cdn) {
super.qty(doc, cdt, cdn);

View File

@@ -281,7 +281,9 @@ def get_employee_email(employee_doc):
def get_holiday_list_for_employee(employee, raise_exception=True):
if employee:
holiday_list, company = frappe.db.get_value("Employee", employee, ["holiday_list", "company"])
holiday_list, company = frappe.get_cached_value(
"Employee", employee, ["holiday_list", "company"]
)
else:
holiday_list = ""
company = frappe.db.get_single_value("Global Defaults", "default_company")

View File

@@ -115,6 +115,8 @@ def is_holiday(holiday_list, date=None):
if date is None:
date = today()
if holiday_list:
return bool(frappe.get_all("Holiday List", dict(name=holiday_list, holiday_date=date)))
return bool(
frappe.db.exists("Holiday", {"parent": holiday_list, "holiday_date": date}, cache=True)
)
else:
return False

View File

@@ -22,24 +22,18 @@
"creation": "2021-11-22 12:19:15.888642",
"docstatus": 0,
"doctype": "Module Onboarding",
"documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup",
"documentation_url": "https://docs.erpnext.com/docs/v14/user/manual/en/setting-up/company-setup",
"idx": 0,
"is_complete": 0,
"modified": "2022-06-07 14:31:00.575193",
"modified": "2023-05-16 13:13:24.043792",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",
"owner": "Administrator",
"steps": [
{
"step": "Company Set Up"
},
{
"step": "Navigation Help"
},
{
"step": "Data import"
},
{
"step": "Create an Item"
},
@@ -51,12 +45,9 @@
},
{
"step": "Create a Quotation"
},
{
"step": "Letterhead"
}
],
"subtitle": "Company, Item, Customer, Supplier, Navigation Help, Data Import, Letter Head, Quotation",
"success_message": "Masters are all set up!",
"title": "Let's Set Up Some Masters"
"subtitle": "Item, Customer, Supplier, Navigation Help and Quotation",
"success_message": "You're ready to start your journey with ERPNext",
"title": "Let's begin your journey with ERPNext"
}

View File

@@ -5,11 +5,11 @@
"description": "# Set Up a Company\n\nA company is a legal entity for which you will set up your books of account and create accounting transactions. In ERPNext, you can create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company.\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"idx": 1,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-12-15 14:22:18.317423",
"modified": "2023-05-15 09:18:42.895537",
"modified_by": "Administrator",
"name": "Company Set Up",
"owner": "Administrator",

View File

@@ -5,17 +5,17 @@
"description": "# Create a Customer\n\nThe Customer master is at the heart of your sales transactions. Customers are linked in Quotations, Sales Orders, Invoices, and Payments. Customers can be either numbered or identified by name (you would typically do this based on the number of customers you have).\n\nThrough Customer\u2019s master, you can effectively track essentials like:\n - Customer\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"idx": 1,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-12-15 14:20:31.197564",
"modified": "2023-05-16 12:54:54.112364",
"modified_by": "Administrator",
"name": "Create a Customer",
"owner": "Administrator",
"reference_document": "Customer",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Manage Customers",
"title": "Create a Customer",
"validate_action": 1
}

View File

@@ -5,11 +5,11 @@
"description": "# Create a Quotation\n\nLet\u2019s get started with business transactions by creating your first Quotation. You can create a Quotation for an existing customer or a prospect. It will be an approved document, with items you sell and the proposed price + taxes applied. After completing the instructions, you will get a Quotation in a ready to share print format.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"idx": 1,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-12-15 14:21:31.675330",
"modified": "2023-05-15 09:18:42.984170",
"modified_by": "Administrator",
"name": "Create a Quotation",
"owner": "Administrator",

View File

@@ -5,17 +5,17 @@
"description": "# Create a Supplier\n\nAlso known as Vendor, is a master at the center of your purchase transactions. Suppliers are linked in Request for Quotation, Purchase Orders, Receipts, and Payments. Suppliers can be either numbered or identified by name.\n\nThrough Supplier\u2019s master, you can effectively track essentials like:\n - Supplier\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"idx": 1,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-12-15 14:21:23.518301",
"modified": "2023-05-16 12:55:08.610113",
"modified_by": "Administrator",
"name": "Create a Supplier",
"owner": "Administrator",
"reference_document": "Supplier",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Manage Suppliers",
"title": "Create a Supplier",
"validate_action": 1
}

View File

@@ -6,18 +6,18 @@
"docstatus": 0,
"doctype": "Onboarding Step",
"form_tour": "Item General",
"idx": 0,
"idx": 1,
"intro_video_url": "",
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-12-15 14:19:56.297772",
"modified": "2023-05-16 12:56:40.355878",
"modified_by": "Administrator",
"name": "Create an Item",
"owner": "Administrator",
"reference_document": "Item",
"show_form_tour": 1,
"show_full_form": 1,
"title": "Manage Items",
"show_full_form": 0,
"title": "Create an Item",
"validate_action": 1
}

View File

@@ -5,11 +5,11 @@
"description": "# Import Data from Spreadsheet\n\nIn ERPNext, you can easily migrate your historical data using spreadsheets. You can use it for migrating not just masters (like Customer, Supplier, Items), but also for transactions like (outstanding invoices, opening stock and accounting entries, etc).",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"idx": 1,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2022-06-07 14:28:51.390813",
"modified": "2023-05-15 09:18:42.962231",
"modified_by": "Administrator",
"name": "Data import",
"owner": "Administrator",

View File

@@ -5,11 +5,11 @@
"description": "# Create a Letter Head\n\nA Letter Head contains your organization's name, logo, address, etc which appears at the header and footer portion in documents. You can learn more about Setting up Letter Head in ERPNext here.\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"idx": 1,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-12-15 14:21:39.037742",
"modified": "2023-05-15 09:18:42.995184",
"modified_by": "Administrator",
"name": "Letterhead",
"owner": "Administrator",

View File

@@ -2,14 +2,14 @@
"action": "Watch Video",
"action_label": "Learn about Navigation options",
"creation": "2021-11-22 12:09:52.233872",
"description": "# Navigation in ERPNext\n\nEase of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or awesome bar\u2019s shortcut.\n",
"description": "# Navigation in ERPNext\n\nEase of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or AwesomeBar.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"idx": 1,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2022-06-07 14:28:00.901082",
"modified": "2023-05-16 12:53:25.939908",
"modified_by": "Administrator",
"name": "Navigation Help",
"owner": "Administrator",

View File

@@ -21,6 +21,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("CRM Settings", "default_valid_till")
)

View File

@@ -75,7 +75,16 @@ class InventoryDimension(Document):
self.delete_custom_fields()
def delete_custom_fields(self):
filters = {"fieldname": self.source_fieldname}
filters = {
"fieldname": (
"in",
[
self.source_fieldname,
f"to_{self.source_fieldname}",
f"from_{self.source_fieldname}",
],
)
}
if self.document_type:
filters["dt"] = self.document_type
@@ -88,6 +97,8 @@ class InventoryDimension(Document):
def reset_value(self):
if self.apply_to_all_doctypes:
self.type_of_transaction = ""
self.istable = 0
for field in ["document_type", "condition"]:
self.set(field, None)
@@ -111,12 +122,35 @@ class InventoryDimension(Document):
def on_update(self):
self.add_custom_fields()
def add_custom_fields(self):
dimension_fields = [
@staticmethod
def get_insert_after_fieldname(doctype):
return frappe.get_all(
"DocField",
fields=["fieldname"],
filters={"parent": doctype},
order_by="idx desc",
limit=1,
)[0].fieldname
def get_dimension_fields(self, doctype=None):
if not doctype:
doctype = self.document_type
label_start_with = ""
if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
label_start_with = "Target"
elif doctype in ["Sales Invoice Item", "Delivery Note Item", "Stock Entry Detail"]:
label_start_with = "Source"
label = self.dimension_name
if label_start_with:
label = f"{label_start_with} {self.dimension_name}"
return [
dict(
fieldname="inventory_dimension",
fieldtype="Section Break",
insert_after="warehouse",
insert_after=self.get_insert_after_fieldname(doctype),
label="Inventory Dimension",
collapsible=1,
),
@@ -125,24 +159,37 @@ class InventoryDimension(Document):
fieldtype="Link",
insert_after="inventory_dimension",
options=self.reference_document,
label=self.dimension_name,
label=label,
reqd=self.reqd,
mandatory_depends_on=self.mandatory_depends_on,
),
]
def add_custom_fields(self):
custom_fields = {}
dimension_fields = []
if self.apply_to_all_doctypes:
for doctype in get_inventory_documents():
if not field_exists(doctype[0], self.source_fieldname):
custom_fields.setdefault(doctype[0], dimension_fields)
if field_exists(doctype[0], self.source_fieldname):
continue
dimension_fields = self.get_dimension_fields(doctype[0])
self.add_transfer_field(doctype[0], dimension_fields)
custom_fields.setdefault(doctype[0], dimension_fields)
elif not field_exists(self.document_type, self.source_fieldname):
dimension_fields = self.get_dimension_fields()
self.add_transfer_field(self.document_type, dimension_fields)
custom_fields.setdefault(self.document_type, dimension_fields)
if not frappe.db.get_value(
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
) and not field_exists("Stock Ledger Entry", self.target_fieldname):
if (
dimension_fields
and not frappe.db.get_value(
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
)
and not field_exists("Stock Ledger Entry", self.target_fieldname)
):
dimension_field = dimension_fields[1]
dimension_field["mandatory_depends_on"] = ""
dimension_field["reqd"] = 0
@@ -152,6 +199,53 @@ class InventoryDimension(Document):
if custom_fields:
create_custom_fields(custom_fields)
def add_transfer_field(self, doctype, dimension_fields):
if doctype not in [
"Stock Entry Detail",
"Sales Invoice Item",
"Delivery Note Item",
"Purchase Invoice Item",
"Purchase Receipt Item",
]:
return
fieldname_start_with = "to"
label_start_with = "Target"
display_depends_on = ""
if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
fieldname_start_with = "from"
label_start_with = "Source"
display_depends_on = "eval:parent.is_internal_supplier == 1"
elif doctype != "Stock Entry Detail":
display_depends_on = "eval:parent.is_internal_customer == 1"
elif doctype == "Stock Entry Detail":
display_depends_on = "eval:parent.purpose != 'Material Issue'"
fieldname = f"{fieldname_start_with}_{self.source_fieldname}"
label = f"{label_start_with} {self.dimension_name}"
if field_exists(doctype, fieldname):
return
dimension_fields.extend(
[
dict(
fieldname="inventory_dimension_col_break",
fieldtype="Column Break",
insert_after=self.source_fieldname,
),
dict(
fieldname=fieldname,
fieldtype="Link",
insert_after="inventory_dimension_col_break",
options=self.reference_document,
label=label,
depends_on=display_depends_on,
),
]
)
def field_exists(doctype, fieldname) -> str or None:
return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
@@ -185,18 +279,19 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
dimensions = get_document_wise_inventory_dimensions(doc.doctype)
filter_dimensions = []
for row in dimensions:
if (
row.type_of_transaction == "Inward"
if doc.docstatus == 1
else row.type_of_transaction != "Inward"
) and sl_dict.actual_qty < 0:
continue
elif (
row.type_of_transaction == "Outward"
if doc.docstatus == 1
else row.type_of_transaction != "Outward"
) and sl_dict.actual_qty > 0:
continue
if row.type_of_transaction:
if (
row.type_of_transaction == "Inward"
if doc.docstatus == 1
else row.type_of_transaction != "Inward"
) and sl_dict.actual_qty < 0:
continue
elif (
row.type_of_transaction == "Outward"
if doc.docstatus == 1
else row.type_of_transaction != "Outward"
) and sl_dict.actual_qty > 0:
continue
evals = {"doc": doc}
if parent_doc:

View File

@@ -4,6 +4,7 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate, nowtime
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
@@ -12,6 +13,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
DoNotChangeError,
delete_dimension,
)
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -20,6 +22,7 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
class TestInventoryDimension(FrappeTestCase):
def setUp(self):
prepare_test_data()
create_store_dimension()
def test_validate_inventory_dimension(self):
# Can not be child doc
@@ -73,6 +76,8 @@ class TestInventoryDimension(FrappeTestCase):
self.assertFalse(custom_field)
def test_inventory_dimension(self):
frappe.local.document_wise_inventory_dimensions = {}
warehouse = "Shelf Warehouse - _TC"
item_code = "_Test Item"
@@ -143,6 +148,8 @@ class TestInventoryDimension(FrappeTestCase):
self.assertRaises(DoNotChangeError, inv_dim1.save)
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
frappe.local.document_wise_inventory_dimensions = {}
inv_dimension = create_inventory_dimension(
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
)
@@ -250,6 +257,191 @@ class TestInventoryDimension(FrappeTestCase):
)
)
def test_for_purchase_sales_and_stock_transaction(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
create_inventory_dimension(
reference_document="Store",
type_of_transaction="Outward",
dimension_name="Store",
apply_to_all_doctypes=1,
)
item_code = "Test Inventory Dimension Item"
create_item(item_code)
warehouse = create_warehouse("Store Warehouse")
# Purchase Receipt -> Inward in Store 1
pr_doc = make_purchase_receipt(
item_code=item_code, warehouse=warehouse, qty=10, rate=100, do_not_submit=True
)
pr_doc.items[0].store = "Store 1"
pr_doc.save()
pr_doc.submit()
entries = get_voucher_sl_entries(pr_doc.name, ["warehouse", "store", "incoming_rate"])
self.assertEqual(entries[0].warehouse, warehouse)
self.assertEqual(entries[0].store, "Store 1")
# Stock Entry -> Transfer from Store 1 to Store 2
se_doc = make_stock_entry(
item_code=item_code, qty=10, from_warehouse=warehouse, to_warehouse=warehouse, do_not_save=True
)
se_doc.items[0].store = "Store 1"
se_doc.items[0].to_store = "Store 2"
se_doc.save()
se_doc.submit()
entries = get_voucher_sl_entries(
se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"]
)
for entry in entries:
self.assertEqual(entry.warehouse, warehouse)
if entry.actual_qty > 0:
self.assertEqual(entry.store, "Store 2")
self.assertEqual(entry.incoming_rate, 100.0)
else:
self.assertEqual(entry.store, "Store 1")
# Delivery Note -> Outward from Store 2
dn_doc = create_delivery_note(item_code=item_code, qty=10, warehouse=warehouse, do_not_save=True)
dn_doc.items[0].store = "Store 2"
dn_doc.save()
dn_doc.submit()
entries = get_voucher_sl_entries(dn_doc.name, ["warehouse", "store", "actual_qty"])
self.assertEqual(entries[0].warehouse, warehouse)
self.assertEqual(entries[0].store, "Store 2")
self.assertEqual(entries[0].actual_qty, -10.0)
return_dn = make_return_doc("Delivery Note", dn_doc.name)
return_dn.submit()
entries = get_voucher_sl_entries(return_dn.name, ["warehouse", "store", "actual_qty"])
self.assertEqual(entries[0].warehouse, warehouse)
self.assertEqual(entries[0].store, "Store 2")
self.assertEqual(entries[0].actual_qty, 10.0)
se_doc = make_stock_entry(
item_code=item_code, qty=10, from_warehouse=warehouse, to_warehouse=warehouse, do_not_save=True
)
se_doc.items[0].store = "Store 2"
se_doc.items[0].to_store = "Store 1"
se_doc.save()
se_doc.submit()
return_pr = make_return_doc("Purchase Receipt", pr_doc.name)
return_pr.submit()
entries = get_voucher_sl_entries(return_pr.name, ["warehouse", "store", "actual_qty"])
self.assertEqual(entries[0].warehouse, warehouse)
self.assertEqual(entries[0].store, "Store 1")
self.assertEqual(entries[0].actual_qty, -10.0)
def test_inter_transfer_return_against_inventory_dimension(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
data = prepare_data_for_internal_transfer()
dn_doc = create_delivery_note(
customer=data.customer,
company=data.company,
warehouse=data.from_warehouse,
target_warehouse=data.to_warehouse,
qty=5,
cost_center=data.cost_center,
expense_account=data.expense_account,
do_not_submit=True,
)
dn_doc.items[0].store = "Inter Transfer Store 1"
dn_doc.items[0].to_store = "Inter Transfer Store 2"
dn_doc.save()
dn_doc.submit()
for d in get_voucher_sl_entries(dn_doc.name, ["store", "actual_qty"]):
if d.actual_qty > 0:
self.assertEqual(d.store, "Inter Transfer Store 2")
else:
self.assertEqual(d.store, "Inter Transfer Store 1")
pr_doc = make_inter_company_purchase_receipt(dn_doc.name)
pr_doc.items[0].warehouse = data.store_warehouse
pr_doc.items[0].from_store = "Inter Transfer Store 2"
pr_doc.items[0].store = "Inter Transfer Store 3"
pr_doc.save()
pr_doc.submit()
for d in get_voucher_sl_entries(pr_doc.name, ["store", "actual_qty"]):
if d.actual_qty > 0:
self.assertEqual(d.store, "Inter Transfer Store 3")
else:
self.assertEqual(d.store, "Inter Transfer Store 2")
return_doc = make_return_doc("Purchase Receipt", pr_doc.name)
return_doc.submit()
for d in get_voucher_sl_entries(return_doc.name, ["store", "actual_qty"]):
if d.actual_qty > 0:
self.assertEqual(d.store, "Inter Transfer Store 2")
else:
self.assertEqual(d.store, "Inter Transfer Store 3")
dn_doc.load_from_db()
return_doc1 = make_return_doc("Delivery Note", dn_doc.name)
return_doc1.posting_date = nowdate()
return_doc1.posting_time = nowtime()
return_doc1.items[0].target_warehouse = dn_doc.items[0].target_warehouse
return_doc1.items[0].warehouse = dn_doc.items[0].warehouse
return_doc1.save()
return_doc1.submit()
for d in get_voucher_sl_entries(return_doc1.name, ["store", "actual_qty"]):
if d.actual_qty > 0:
self.assertEqual(d.store, "Inter Transfer Store 1")
else:
self.assertEqual(d.store, "Inter Transfer Store 2")
def get_voucher_sl_entries(voucher_no, fields):
return frappe.get_all(
"Stock Ledger Entry", filters={"voucher_no": voucher_no}, fields=fields, order_by="creation"
)
def create_store_dimension():
if not frappe.db.exists("DocType", "Store"):
frappe.get_doc(
{
"doctype": "DocType",
"name": "Store",
"module": "Stock",
"custom": 1,
"naming_rule": "By fieldname",
"autoname": "field:store_name",
"fields": [{"label": "Store Name", "fieldname": "store_name", "fieldtype": "Data"}],
"permissions": [
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
],
}
).insert(ignore_permissions=True)
for store in ["Store 1", "Store 2"]:
if not frappe.db.exists("Store", store):
frappe.get_doc({"doctype": "Store", "store_name": store}).insert(ignore_permissions=True)
def prepare_test_data():
if not frappe.db.exists("DocType", "Shelf"):
@@ -326,3 +518,79 @@ def create_inventory_dimension(**args):
doc.insert(ignore_permissions=True)
return doc
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = "_Test Company with perpetual inventory"
customer = create_internal_customer(
"_Test Internal Customer 2",
company,
company,
)
supplier = create_internal_supplier(
"_Test Internal Supplier 2",
company,
company,
)
for store in ["Inter Transfer Store 1", "Inter Transfer Store 2", "Inter Transfer Store 3"]:
if not frappe.db.exists("Store", store):
frappe.get_doc({"doctype": "Store", "store_name": store}).insert(ignore_permissions=True)
warehouse = create_warehouse("_Test Internal Warehouse New A", company=company)
to_warehouse = create_warehouse("_Test Internal Warehouse GIT A", company=company)
pr_doc = make_purchase_receipt(
company=company, warehouse=warehouse, qty=10, rate=100, do_not_submit=True
)
pr_doc.items[0].store = "Inter Transfer Store 1"
pr_doc.submit()
if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"):
account = "Unrealized Profit and Loss - TCP1"
if not frappe.db.exists("Account", account):
frappe.get_doc(
{
"doctype": "Account",
"account_name": "Unrealized Profit and Loss",
"parent_account": "Direct Income - TCP1",
"company": company,
"is_group": 0,
"account_type": "Income Account",
}
).insert()
frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account)
cost_center = frappe.db.get_value("Company", company, "cost_center") or frappe.db.get_value(
"Cost Center", {"company": company}, "name"
)
expense_account = frappe.db.get_value(
"Company", company, "stock_adjustment_account"
) or frappe.db.get_value(
"Account", {"company": company, "account_type": "Expense Account"}, "name"
)
return frappe._dict(
{
"from_warehouse": warehouse,
"to_warehouse": to_warehouse,
"customer": customer,
"supplier": supplier,
"company": company,
"cost_center": cost_center,
"expense_account": expense_account,
"store_warehouse": frappe.db.get_value(
"Warehouse", {"name": ("like", "Store%"), "company": company}, "name"
),
}
)

View File

@@ -1,5 +1,5 @@
frappe.listview_settings['Item'] = {
add_fields: ["item_name", "stock_uom", "item_group", "image", "variant_of",
add_fields: ["item_name", "stock_uom", "item_group", "image",
"has_variants", "end_of_life", "disabled"],
filters: [["disabled", "=", "0"]],

View File

@@ -172,8 +172,8 @@ class PickList(Document):
if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance:
frappe.throw(
_(
f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}."
)
"You are picking more than required quantity for the item {0}. Check if there is any other pick list created for the sales order {1}."
).format(row.item_code, row.sales_order)
)
@frappe.whitelist()

View File

@@ -376,3 +376,19 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
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

@@ -9,7 +9,17 @@ import frappe
from frappe import _
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder.functions import Sum
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
from frappe.utils import (
cint,
comma_or,
cstr,
flt,
format_time,
formatdate,
getdate,
month_diff,
nowdate,
)
import erpnext
from erpnext.accounts.general_ledger import process_gl_map
@@ -154,6 +164,41 @@ class StockEntry(StockController):
self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
def submit(self):
if self.is_enqueue_action():
frappe.msgprint(
_(
"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"
)
)
self.queue_action("submit", timeout=2000)
else:
self._submit()
def cancel(self):
if self.is_enqueue_action():
frappe.msgprint(
_(
"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"
)
)
self.queue_action("cancel", timeout=2000)
else:
self._cancel()
def is_enqueue_action(self, force=False) -> bool:
if force:
return True
if frappe.flags.in_test:
return False
# If line items are more than 100 or record is older than 6 months
if len(self.items) > 100 or month_diff(nowdate(), self.posting_date) > 6:
return True
return False
def on_submit(self):
self.update_stock_ledger()

View File

@@ -5,7 +5,7 @@
import frappe
from frappe.permissions import add_user_permission, remove_user_permission
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, nowdate, nowtime, today
from frappe.utils import add_days, add_to_date, flt, nowdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import (
@@ -1707,6 +1707,36 @@ class TestStockEntry(FrappeTestCase):
self.assertRaises(frappe.ValidationError, sr_doc.submit)
def test_enqueue_action(self):
frappe.flags.in_test = False
item_code = "Test Enqueue Item - 001"
create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
doc = make_stock_entry(
item_code=item_code,
posting_date=add_to_date(today(), months=-7),
posting_time="00:00:00",
purpose="Material Receipt",
qty=10,
to_warehouse="_Test Warehouse - _TC",
do_not_submit=True,
)
self.assertTrue(doc.is_enqueue_action())
doc = make_stock_entry(
item_code=item_code,
posting_date=today(),
posting_time="00:00:00",
purpose="Material Receipt",
qty=10,
to_warehouse="_Test Warehouse - _TC",
do_not_submit=True,
)
self.assertFalse(doc.is_enqueue_action())
frappe.flags.in_test = True
def make_serialized_item(**args):
args = frappe._dict(args)

View File

@@ -781,13 +781,21 @@ class update_entries_after(object):
d.db_update()
def update_rate_on_subcontracting_receipt(self, sle, outgoing_rate):
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "rate", outgoing_rate)
if frappe.db.exists("Subcontracting Receipt Item", sle.voucher_detail_no):
frappe.db.set_value("Subcontracting Receipt Item", sle.voucher_detail_no, "rate", outgoing_rate)
else:
frappe.db.set_value(
"Subcontracting Receipt Supplied Item", sle.voucher_detail_no, "rate", outgoing_rate
"Subcontracting Receipt Supplied Item",
sle.voucher_detail_no,
{"rate": outgoing_rate, "amount": abs(sle.actual_qty) * outgoing_rate},
)
scr = frappe.get_doc("Subcontracting Receipt", sle.voucher_no, for_update=True)
scr.calculate_items_qty_and_amount()
scr.db_update()
for d in scr.items:
d.db_update()
def get_serialized_values(self, sle):
incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty)

View File

@@ -77,22 +77,22 @@ class SubcontractingOrder(SubcontractingController):
frappe.throw(_(msg))
def set_missing_values(self):
self.set_missing_values_in_additional_costs()
self.set_missing_values_in_service_items()
self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items()
self.calculate_additional_costs()
self.calculate_service_costs()
self.calculate_supplied_items_qty_and_amount()
self.calculate_items_qty_and_amount()
def set_missing_values_in_service_items(self):
def calculate_service_costs(self):
for idx, item in enumerate(self.get("service_items")):
self.items[idx].service_cost_per_qty = item.amount / self.items[idx].qty
def set_missing_values_in_supplied_items(self):
def calculate_supplied_items_qty_and_amount(self):
for item in self.get("items"):
bom = frappe.get_doc("BOM", item.bom)
rm_cost = sum(flt(rm_item.amount) for rm_item in bom.items)
item.rm_cost_per_qty = rm_cost / flt(bom.quantity)
def set_missing_values_in_items(self):
def calculate_items_qty_and_amount(self):
total_qty = total = 0
for item in self.items:
item.rate = item.rm_cost_per_qty + item.service_cost_per_qty + flt(item.additional_cost_per_qty)

View File

@@ -113,9 +113,9 @@ class SubcontractingReceipt(SubcontractingController):
@frappe.whitelist()
def set_missing_values(self):
self.set_missing_values_in_additional_costs()
self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items()
self.calculate_additional_costs()
self.calculate_supplied_items_qty_and_amount()
self.calculate_items_qty_and_amount()
def set_available_qty_for_consumption(self):
supplied_items_details = {}
@@ -147,13 +147,13 @@ class SubcontractingReceipt(SubcontractingController):
item.rm_item_code, 0
)
def set_missing_values_in_supplied_items(self):
def calculate_supplied_items_qty_and_amount(self):
for item in self.get("supplied_items") or []:
item.amount = item.rate * item.consumed_qty
self.set_available_qty_for_consumption()
def set_missing_values_in_items(self):
def calculate_items_qty_and_amount(self):
rm_supp_cost = {}
for item in self.get("supplied_items") or []:
if item.reference_name in rm_supp_cost:

View File

@@ -6,7 +6,7 @@ import copy
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import cint, flt
from frappe.utils import add_days, cint, cstr, flt, today
import erpnext
from erpnext.accounts.doctype.account.test_account import get_inventory_account
@@ -26,6 +26,9 @@ from erpnext.controllers.tests.test_subcontracting_controller import (
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
)
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
make_subcontracting_receipt,
)
@@ -528,6 +531,69 @@ class TestSubcontractingReceipt(FrappeTestCase):
# consumed_qty should be (accepted_qty * qty_consumed_per_unit) = (6 * 1) = 6
self.assertEqual(scr.supplied_items[0].consumed_qty, 6)
def test_supplied_items_cost_after_reposting(self):
# Set Backflush Based On as "BOM"
set_backflush_based_on("BOM")
# Create Material Receipt for RM's
make_stock_entry(
item_code="_Test Item",
qty=100,
target="_Test Warehouse 1 - _TC",
basic_rate=100,
posting_date=add_days(today(), -2),
)
make_stock_entry(
item_code="_Test Item Home Desktop 100",
qty=100,
target="_Test Warehouse 1 - _TC",
basic_rate=100,
)
service_items = [
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Service Item 1",
"qty": 10,
"rate": 100,
"fg_item": "_Test FG Item",
"fg_item_qty": 10,
},
]
# Create Subcontracting Order
sco = get_subcontracting_order(service_items=service_items)
# Transfer RM's
rm_items = get_rm_items(sco.supplied_items)
itemwise_details = make_stock_in_entry(rm_items=rm_items)
make_stock_transfer_entry(
sco_no=sco.name,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
# Create Subcontracting Receipt
scr = make_subcontracting_receipt(sco.name)
scr.save()
scr.submit()
# Create Backdated Stock Reconciliation
sr = create_stock_reconciliation(
item_code=rm_items[0].get("item_code"),
warehouse="_Test Warehouse 1 - _TC",
qty=100,
rate=50,
posting_date=add_days(today(), -1),
)
# Cost should be updated in Subcontracting Receipt after reposting
prev_cost = scr.supplied_items[0].rate
scr.load_from_db()
self.assertNotEqual(scr.supplied_items[0].rate, prev_cost)
self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate)
def make_return_subcontracting_receipt(**args):
args = frappe._dict(args)

View File

@@ -776,6 +776,9 @@ def on_communication_update(doc, status):
if not parent.meta.has_field("service_level_agreement"):
return
if not parent.get("service_level_agreement"):
return
if (
doc.sent_or_received == "Received" # a reply is received
and parent.get("status") == "Open" # issue status is set as open from communication.py