Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into e-commerce-refactor

This commit is contained in:
marination
2021-08-30 22:30:02 +05:30
296 changed files with 1771 additions and 5398 deletions

View File

@@ -4,11 +4,7 @@ set -e
cd ~ || exit
sudo apt-get install redis-server
sudo apt install nodejs
sudo apt install npm
sudo apt-get install redis-server libcups2-dev
pip install frappe-bench
@@ -32,7 +28,6 @@ wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/w
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
sudo chmod o+x /usr/local/bin/wkhtmltopdf
sudo apt-get install libcups2-dev
cd ~/frappe-bench || exit

View File

@@ -32,6 +32,12 @@ jobs:
with:
python-version: 3.6
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 12
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts

View File

@@ -42,6 +42,12 @@ jobs:
with:
python-version: 3.7
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 12
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts

View File

@@ -94,7 +94,7 @@ jobs:
run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests
- name: cypress pre-requisites
run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 --no-lockfile
run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile
- name: Build Assets

View File

@@ -1,7 +1,7 @@
import frappe
from frappe import _
from frappe.contacts.doctype.address.address import Address
from frappe.contacts.doctype.address.address import get_address_templates
from frappe.contacts.doctype.address.address import get_address_templates, get_address_display
class ERPNextAddress(Address):
def validate(self):
@@ -22,6 +22,16 @@ class ERPNextAddress(Address):
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
title=_("Company Not Linked"))
def on_update(self):
"""
After Address is updated, update the related 'Primary Address' on Customer.
"""
address_display = get_address_display(self.as_dict())
filters = { "customer_primary_address": self.name }
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
for customer_name in customers:
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
@frappe.whitelist()
def get_shipping_address(company, address = None):
filters = [

View File

@@ -74,7 +74,7 @@ frappe.ui.form.on('Account', {
});
} else if (cint(frm.doc.is_group) == 0
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
cur_frm.add_custom_button(__('Ledger'), function () {
frm.add_custom_button(__('Ledger'), function () {
frappe.route_options = {
"account": frm.doc.name,
"from_date": frappe.sys_defaults.year_start_date,

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Accounting Period", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Accounting Period
() => frappe.tests.make('Accounting Period', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Bank", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank
() => frappe.tests.make('Bank', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Bank Account", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Account
() => frappe.tests.make('Bank Account', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Bank Account Subtype", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Account Subtype
() => frappe.tests.make('Bank Account Subtype', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Bank Guarantee", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Guarantee
() => frappe.tests.make('Bank Guarantee', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -55,6 +55,11 @@ class BankTransaction(StatusUpdater):
self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
def clear_simple_entry(self, payment_entry, for_cancel=False):
if payment_entry.payment_document == "Payment Entry":
if frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type") == "Internal Transfer":
if len(get_reconciled_bank_transactions(payment_entry)) < 2:
return
clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
payment_entry.payment_document, payment_entry.payment_entry,
@@ -70,6 +75,17 @@ class BankTransaction(StatusUpdater):
),
"clearance_date", clearance_date)
def get_reconciled_bank_transactions(payment_entry):
reconciled_bank_transactions = frappe.get_all(
'Bank Transaction Payments',
filters = {
'payment_entry': payment_entry.payment_entry
},
fields = ['parent']
)
return reconciled_bank_transactions
def get_total_allocated_amount(payment_entry):
return frappe.db.sql("""
SELECT

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Bank Transaction", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Transaction
() => frappe.tests.make('Bank Transaction', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cash Flow Mapper", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cash Flow Mapper
() => frappe.tests.make('Cash Flow Mapper', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cash Flow Mapping", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cash Flow Mapping
() => frappe.tests.make('Cash Flow Mapping', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cash Flow Mapping Template", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cash Flow Mapping Template
() => frappe.tests.make('Cash Flow Mapping Template', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cash Flow Mapping Template Details", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cash Flow Mapping Template Details
() => frappe.tests.make('Cash Flow Mapping Template Details', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Cashier Closing", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Cashier Closing
() => frappe.tests.make('Cashier Closing', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Chart of Accounts Importer", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Chart of Accounts Importer
() => frappe.tests.make('Chart of Accounts Importer', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Coupon Code", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Coupon Code
() => frappe.tests.make('Coupon Code', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Exchange Rate Revaluation", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Exchange Rate Revaluation
() => frappe.tests.make('Exchange Rate Revaluation', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Finance Book", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Finance Book
() => frappe.tests.make('Finance Book', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: GL Entry", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially('GL Entry', [
// insert a new GL Entry
() => frappe.tests.make([
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Item Tax Template", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Item Tax Template
() => frappe.tests.make('Item Tax Template', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -643,7 +643,10 @@ class JournalEntry(AccountsController):
for d in self.accounts:
if d.reference_type=="Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc, jv=self.name)
if self.docstatus == 2:
update_reimbursed_amount(doc, -1 * d.debit)
else:
update_reimbursed_amount(doc, d.debit)
def validate_expense_claim(self):

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Loyalty Point Entry", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Loyalty Point Entry
() => frappe.tests.make('Loyalty Point Entry', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Loyalty Program", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Loyalty Program
() => frappe.tests.make('Loyalty Program', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Opening Invoice Creation Tool", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Opening Invoice Creation Tool
() => frappe.tests.make('Opening Invoice Creation Tool', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -75,9 +75,9 @@ class PaymentEntry(AccountsController):
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.make_gl_entries()
self.update_expense_claim()
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_expense_claim()
self.update_donation()
self.update_payment_schedule()
self.set_status()
@@ -85,9 +85,9 @@ class PaymentEntry(AccountsController):
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.make_gl_entries(cancel=1)
self.update_expense_claim()
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_expense_claim()
self.update_donation(cancel=1)
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
@@ -755,9 +755,11 @@ class PaymentEntry(AccountsController):
if self.payment_type in ('Pay', 'Internal Transfer'):
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_from
elif self.payment_type == 'Receive':
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_to
payment_or_advance_account = self.get_party_account_for_taxes()
@@ -779,14 +781,13 @@ class PaymentEntry(AccountsController):
"cost_center": d.cost_center
}, account_currency, item=d))
#Intentionally use -1 to get net values in party account
if not d.included_in_paid_amount or self.advance_tax_account:
gl_entries.append(
self.get_gl_dict({
"account": payment_or_advance_account,
"against": against,
dr_or_cr: -1 * tax_amount,
dr_or_cr + "_in_account_currency": -1 * base_tax_amount
rev_dr_or_cr: tax_amount,
rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": self.cost_center,
@@ -830,7 +831,10 @@ class PaymentEntry(AccountsController):
for d in self.get("references"):
if d.reference_doctype=="Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc, self.name)
if self.docstatus == 2:
update_reimbursed_amount(doc, -1 * d.allocated_amount)
else:
update_reimbursed_amount(doc, d.allocated_amount)
def update_donation(self, cancel=0):
if self.payment_type == "Receive" and self.party_type == "Donor" and self.party:

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Payment Order", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Payment Order
() => frappe.tests.make('Payment Order', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Payment Request", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Payment Request
() => frappe.tests.make('Payment Request', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Payment Term", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Payment Term
() => frappe.tests.make('Payment Term', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Payment Terms Template", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Payment Terms Template
() => frappe.tests.make('Payment Terms Template', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -13,59 +13,49 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
class TestPeriodClosingVoucher(unittest.TestCase):
def test_closing_entry(self):
year_start_date = get_fiscal_year(today(), company="_Test Company")[1]
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
make_journal_entry("_Test Bank - _TC", "Sales - _TC", 400,
"_Test Cost Center - _TC", posting_date=now(), submit=True)
company = create_company()
cost_center = create_cost_center('Test Cost Center 1')
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 600, "_Test Cost Center - _TC", posting_date=now(), submit=True)
jv1 = make_journal_entry(
amount=400,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
posting_date=now(),
save=False
)
jv1.company = company
jv1.save()
jv1.submit()
random_expense_account = frappe.db.sql("""
select t1.account,
sum(t1.debit) - sum(t1.credit) as balance,
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) \
as balance_in_account_currency
from `tabGL Entry` t1, `tabAccount` t2
where t1.account = t2.name and t2.root_type = 'Expense'
and t2.docstatus < 2 and t2.company = '_Test Company'
and t1.posting_date between %s and %s
group by t1.account
having sum(t1.debit) > sum(t1.credit)
limit 1""", (year_start_date, today()), as_dict=True)
profit_or_loss = frappe.db.sql("""select sum(t1.debit) - sum(t1.credit) as balance
from `tabGL Entry` t1, `tabAccount` t2
where t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = '_Test Company'
and t1.posting_date between %s and %s""", (year_start_date, today()))
profit_or_loss = flt(profit_or_loss[0][0]) if profit_or_loss else 0
jv2 = make_journal_entry(
amount=600,
account1="Cost of Goods Sold - TPC",
account2="Cash - TPC",
cost_center=cost_center,
posting_date=now(),
save=False
)
jv2.company = company
jv2.save()
jv2.submit()
pcv = self.make_period_closing_voucher()
surplus_account = pcv.closing_account_head
# Check value for closing account
gle_amount_for_closing_account = frappe.db.sql("""select debit - credit
from `tabGL Entry` where voucher_type='Period Closing Voucher' and voucher_no=%s
and account = '_Test Account Reserves and Surplus - _TC'""", pcv.name)
expected_gle = (
('Cost of Goods Sold - TPC', 0.0, 600.0),
(surplus_account, 600.0, 400.0),
('Sales - TPC', 400.0, 0.0)
)
gle_amount_for_closing_account = flt(gle_amount_for_closing_account[0][0]) \
if gle_amount_for_closing_account else 0
pcv_gle = frappe.db.sql("""
select account, debit, credit from `tabGL Entry` where voucher_no=%s order by account
""", (pcv.name))
self.assertEqual(gle_amount_for_closing_account, profit_or_loss)
if random_expense_account:
# Check posted value for teh above random_expense_account
gle_for_random_expense_account = frappe.db.sql("""
select sum(debit - credit) as amount,
sum(debit_in_account_currency - credit_in_account_currency) as amount_in_account_currency
from `tabGL Entry`
where voucher_type='Period Closing Voucher' and voucher_no=%s and account =%s""",
(pcv.name, random_expense_account[0].account), as_dict=True)
self.assertEqual(gle_for_random_expense_account[0].amount, -1*random_expense_account[0].balance)
self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency,
-1*random_expense_account[0].balance_in_account_currency)
self.assertEqual(pcv_gle, expected_gle)
def test_cost_center_wise_posting(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
@@ -93,31 +83,23 @@ class TestPeriodClosingVoucher(unittest.TestCase):
debit_to="Debtors - TPC"
)
pcv = frappe.get_doc({
"transaction_date": today(),
"posting_date": today(),
"fiscal_year": get_fiscal_year(today())[0],
"company": "Test PCV Company",
"cost_center_wise_pnl": 1,
"closing_account_head": surplus_account,
"remarks": "Test",
"doctype": "Period Closing Voucher"
})
pcv.insert()
pcv.submit()
pcv = self.make_period_closing_voucher()
surplus_account = pcv.closing_account_head
expected_gle = (
('Sales - TPC', 200.0, 0.0, cost_center2),
(surplus_account, 0.0, 400.0, cost_center1),
(surplus_account, 0.0, 200.0, cost_center2),
('Sales - TPC', 400.0, 0.0, cost_center1),
(surplus_account, 0.0, 400.0, cost_center1)
('Sales - TPC', 200.0, 0.0, cost_center2),
)
pcv_gle = frappe.db.sql("""
select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s
select account, debit, credit, cost_center
from `tabGL Entry` where voucher_no=%s
order by account, cost_center
""", (pcv.name))
self.assertTrue(pcv_gle, expected_gle)
self.assertEqual(pcv_gle, expected_gle)
def test_period_closing_with_finance_book_entries(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
@@ -146,39 +128,35 @@ class TestPeriodClosingVoucher(unittest.TestCase):
jv.save()
jv.submit()
pcv = frappe.get_doc({
"transaction_date": today(),
"posting_date": today(),
"fiscal_year": get_fiscal_year(today())[0],
"company": company,
"closing_account_head": surplus_account,
"remarks": "Test",
"doctype": "Period Closing Voucher"
})
pcv.insert()
pcv.submit()
pcv = self.make_period_closing_voucher()
surplus_account = pcv.closing_account_head
expected_gle = (
(surplus_account, 0.0, 400.0, ''),
(surplus_account, 0.0, 400.0, None),
(surplus_account, 0.0, 400.0, jv.finance_book),
('Sales - TPC', 400.0, 0.0, ''),
('Sales - TPC', 400.0, 0.0, None),
('Sales - TPC', 400.0, 0.0, jv.finance_book)
)
pcv_gle = frappe.db.sql("""
select account, debit, credit, finance_book from `tabGL Entry` where voucher_no=%s
select account, debit, credit, finance_book
from `tabGL Entry` where voucher_no=%s
order by account, finance_book
""", (pcv.name))
self.assertTrue(pcv_gle, expected_gle)
self.assertEqual(pcv_gle, expected_gle)
def make_period_closing_voucher(self):
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
pcv = frappe.get_doc({
"doctype": "Period Closing Voucher",
"closing_account_head": "_Test Account Reserves and Surplus - _TC",
"company": "_Test Company",
"fiscal_year": get_fiscal_year(today(), company="_Test Company")[0],
"transaction_date": today(),
"posting_date": today(),
"cost_center": "_Test Cost Center - _TC",
"company": "Test PCV Company",
"fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0],
"cost_center": cost_center,
"closing_account_head": surplus_account,
"remarks": "test"
})
pcv.insert()

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: POS Closing Entry", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new POS Closing Entry
() => frappe.tests.make('POS Closing Entry', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: POS Profile", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially('POS Profile', [
// insert a new POS Profile
() => frappe.tests.make([
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: POS Profile User", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new POS Profile User
() => frappe.tests.make('POS Profile User', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: POS Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new POS Settings
() => frappe.tests.make('POS Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -475,7 +475,20 @@ def apply_pricing_rule_on_transaction(doc):
frappe.msgprint(_("User has not applied rule on the invoice {0}")
.format(doc.name))
else:
doc.set(field, d.get(pr_field))
if not d.coupon_code_based:
doc.set(field, d.get(pr_field))
elif doc.get('coupon_code'):
# coupon code based pricing rule
coupon_code_pricing_rule = frappe.db.get_value('Coupon Code', doc.get('coupon_code'), 'pricing_rule')
if coupon_code_pricing_rule == d.name:
# if selected coupon code is linked with pricing rule
doc.set(field, d.get(pr_field))
else:
# reset discount if not linked
doc.set(field, 0)
else:
# if coupon code based but no coupon code selected
doc.set(field, 0)
doc.calculate_taxes_and_totals()
elif d.price_or_product_discount == 'Product':

View File

@@ -719,19 +719,6 @@ frappe.ui.form.on('Sales Invoice', {
}
},
project: function(frm){
if (!frm.doc.is_return) {
frm.call({
method: "add_timesheet_data",
doc: frm.doc,
callback: function(r, rt) {
refresh_field(['timesheets'])
}
})
frm.refresh();
}
},
onload: function(frm) {
frm.redemption_conversion_factor = null;
},
@@ -842,25 +829,92 @@ frappe.ui.form.on('Sales Invoice', {
}
},
add_timesheet_row: function(frm, row, exchange_rate) {
frm.add_child('timesheets', {
'activity_type': row.activity_type,
'description': row.description,
'time_sheet': row.parent,
'billing_hours': row.billing_hours,
'billing_amount': flt(row.billing_amount) * flt(exchange_rate),
'timesheet_detail': row.name,
'project_name': row.project_name
project: function(frm) {
if (frm.doc.project) {
frm.events.add_timesheet_data(frm, {
project: frm.doc.project
});
}
},
async add_timesheet_data(frm, kwargs) {
if (kwargs === "Sales Invoice") {
// called via frm.trigger()
kwargs = Object();
}
if (!kwargs.hasOwnProperty("project") && frm.doc.project) {
kwargs.project = frm.doc.project;
}
const timesheets = await frm.events.get_timesheet_data(frm, kwargs);
return frm.events.set_timesheet_data(frm, timesheets);
},
async get_timesheet_data(frm, kwargs) {
return frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data",
args: kwargs
}).then(r => {
if (!r.exc && r.message.length > 0) {
return r.message
} else {
return []
}
});
frm.refresh_field('timesheets');
calculate_total_billing_amount(frm);
},
set_timesheet_data: function(frm, timesheets) {
frm.clear_table("timesheets")
timesheets.forEach(timesheet => {
if (frm.doc.currency != timesheet.currency) {
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
from_currency: timesheet.currency,
to_currency: frm.doc.currency
},
callback: function(r) {
if (r.message) {
exchange_rate = r.message;
frm.events.append_time_log(frm, timesheet, exchange_rate);
}
}
});
} else {
frm.events.append_time_log(frm, timesheet, 1.0);
}
});
},
append_time_log: function(frm, time_log, exchange_rate) {
const row = frm.add_child("timesheets");
row.activity_type = time_log.activity_type;
row.description = time_log.description;
row.time_sheet = time_log.time_sheet;
row.from_time = time_log.from_time;
row.to_time = time_log.to_time;
row.billing_hours = time_log.billing_hours;
row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate);
row.timesheet_detail = time_log.name;
row.project_name = time_log.project_name;
frm.refresh_field("timesheets");
frm.trigger("calculate_timesheet_totals");
},
calculate_timesheet_totals: function(frm) {
frm.set_value("total_billing_amount",
frm.doc.timesheets.reduce((a, b) => a + (b["billing_amount"] || 0.0), 0.0));
frm.set_value("total_billing_hours",
frm.doc.timesheets.reduce((a, b) => a + (b["billing_hours"] || 0.0), 0.0));
},
refresh: function(frm) {
if (frm.doc.docstatus===0 && !frm.doc.is_return) {
frm.add_custom_button(__('Fetch Timesheet'), function() {
frm.add_custom_button(__("Fetch Timesheet"), function() {
let d = new frappe.ui.Dialog({
title: __('Fetch Timesheet'),
title: __("Fetch Timesheet"),
fields: [
{
"label" : __("From"),
@@ -869,8 +923,8 @@ frappe.ui.form.on('Sales Invoice', {
"reqd": 1,
},
{
fieldtype: 'Column Break',
fieldname: 'col_break_1',
fieldtype: "Column Break",
fieldname: "col_break_1",
},
{
"label" : __("To"),
@@ -887,48 +941,18 @@ frappe.ui.form.on('Sales Invoice', {
},
],
primary_action: function() {
let data = d.get_values();
frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data",
args: {
from_time: data.from_time,
to_time: data.to_time,
project: data.project
},
callback: function(r) {
if (!r.exc && r.message.length > 0) {
frm.clear_table('timesheets')
r.message.forEach((d) => {
let exchange_rate = 1.0;
if (frm.doc.currency != d.currency) {
frappe.call({
method: 'erpnext.setup.utils.get_exchange_rate',
args: {
from_currency: d.currency,
to_currency: frm.doc.currency
},
callback: function(r) {
if (r.message) {
exchange_rate = r.message;
frm.events.add_timesheet_row(frm, d, exchange_rate);
}
}
});
} else {
frm.events.add_timesheet_row(frm, d, exchange_rate);
}
});
} else {
frappe.msgprint(__('No Timesheets found with the selected filters.'))
}
d.hide();
}
const data = d.get_values();
frm.events.add_timesheet_data(frm, {
from_time: data.from_time,
to_time: data.to_time,
project: data.project
});
d.hide();
},
primary_action_label: __('Get Timesheets')
primary_action_label: __("Get Timesheets")
});
d.show();
})
});
}
if (frm.doc.is_debit_note) {
@@ -961,26 +985,22 @@ frappe.ui.form.on('Sales Invoice', {
frm: frm
});
},
create_dunning: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
frm: frm
});
}
})
});
var calculate_total_billing_amount = function(frm) {
var doc = frm.doc;
doc.total_billing_amount = 0.0
if (doc.timesheets) {
$.each(doc.timesheets, function(index, data){
doc.total_billing_amount += flt(data.billing_amount)
})
frappe.ui.form.on("Sales Invoice Timesheet", {
timesheets_remove(frm, cdt, cdn) {
frm.trigger("calculate_timesheet_totals");
}
});
refresh_field('total_billing_amount')
}
var set_timesheet_detail_rate = function(cdt, cdn, currency, timelog) {
frappe.call({

View File

@@ -74,6 +74,7 @@
"time_sheet_list",
"timesheets",
"total_billing_amount",
"total_billing_hours",
"section_break_30",
"total_qty",
"base_total",
@@ -2011,6 +2012,13 @@
"fieldtype": "Small Text",
"label": "Dispatch Address",
"read_only": 1
},
{
"fieldname": "total_billing_hours",
"fieldtype": "Float",
"label": "Total Billing Hours",
"print_hide": 1,
"read_only": 1
}
],
"icon": "fa fa-file-text",
@@ -2023,7 +2031,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2021-08-25 14:46:05.279588",
"modified": "2021-08-25 15:46:05.279588",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2077,4 +2085,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe, erpnext
import frappe.defaults
from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
from frappe.utils import cint, flt, getdate, add_days, add_months, cstr, nowdate, get_link_to_form, formatdate
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date, get_party_details
from frappe.model.mapper import get_mapped_doc
@@ -13,7 +13,7 @@ from erpnext.accounts.utils import get_account_currency
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
from erpnext.assets.doctype.asset.depreciation \
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, post_depreciation_entries
from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
from erpnext.setup.doctype.company.company import update_company_current_month_sales
@@ -149,7 +149,7 @@ class SalesInvoice(SellingController):
if self.update_stock:
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
elif asset.status in ("Scrapped", "Cancelled", "Sold"):
elif asset.status in ("Scrapped", "Cancelled") or (asset.status == "Sold" and not self.is_return):
frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
def validate_item_cost_centers(self):
@@ -757,7 +757,7 @@ class SalesInvoice(SellingController):
if self.project:
for data in get_projectwise_timesheet_data(self.project):
self.append('timesheets', {
'time_sheet': data.parent,
'time_sheet': data.time_sheet,
'billing_hours': data.billing_hours,
'billing_amount': data.billing_amount,
'timesheet_detail': data.name,
@@ -768,12 +768,11 @@ class SalesInvoice(SellingController):
self.calculate_billing_amount_for_timesheet()
def calculate_billing_amount_for_timesheet(self):
total_billing_amount = 0.0
for data in self.timesheets:
if data.billing_amount:
total_billing_amount += data.billing_amount
def timesheet_sum(field):
return sum((ts.get(field) or 0.0) for ts in self.timesheets)
self.total_billing_amount = total_billing_amount
self.total_billing_amount = timesheet_sum("billing_amount")
self.total_billing_hours = timesheet_sum("billing_hours")
def get_warehouse(self):
user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile`
@@ -925,22 +924,30 @@ class SalesInvoice(SellingController):
for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")):
if item.is_fixed_asset:
asset = frappe.get_doc("Asset", item.asset)
asset = self.get_asset(item)
if (len(asset.finance_books) > 1 and not item.finance_book
and asset.finance_books[0].finance_book):
frappe.throw(_("Select finance book for the item {0} at row {1}")
.format(item.item_code, item.idx))
if self.is_return:
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset,
item.base_net_amount, item.finance_book)
asset.db_set("disposal_date", None)
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset,
item.base_net_amount, item.finance_book)
if asset.calculate_depreciation:
self.reset_depreciation_schedule(asset)
else:
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset,
item.base_net_amount, item.finance_book)
asset.db_set("disposal_date", self.posting_date)
if asset.calculate_depreciation:
self.depreciate_asset(asset)
for gle in fixed_asset_gl_entries:
gle["against"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item))
asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None)
self.set_asset_status(asset)
else:
# Do not book income for transfer within same company
if not self.is_internal_transfer():
@@ -968,6 +975,89 @@ class SalesInvoice(SellingController):
erpnext.is_perpetual_inventory_enabled(self.company):
gl_entries += super(SalesInvoice, self).get_gl_entries()
def get_asset(self, item):
if item.get('asset'):
asset = frappe.get_doc("Asset", item.asset)
else:
frappe.throw(_(
"Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
title=_("Missing Asset")
)
self.check_finance_books(item, asset)
return asset
def check_finance_books(self, item, asset):
if (len(asset.finance_books) > 1 and not item.finance_book
and asset.finance_books[0].finance_book):
frappe.throw(_("Select finance book for the item {0} at row {1}")
.format(item.item_code, item.idx))
def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(self.posting_date)
asset.save()
post_depreciation_entries(self.posting_date)
def reset_depreciation_schedule(self, asset):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data()
self.modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
self.delete_depreciation_entry_made_after_sale(asset)
def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all(
'Asset Repair',
filters = {'asset': asset.name},
fields = ['name', 'increase_in_asset_life']
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc('Asset Repair', repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def delete_depreciation_entry_made_after_sale(self, asset):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice()
row = -1
finance_book = asset.get('schedules')[0].get('finance_book')
for schedule in asset.get('schedules'):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == posting_date_of_original_invoice:
if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
reverse_journal_entry.submit()
def get_posting_date_of_sales_invoice(self):
return frappe.db.get_value('Sales Invoice', self.return_against, 'posting_date')
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def sale_was_made_on_original_schedule_date(self, asset, schedule, row, posting_date_of_original_invoice):
for finance_book in asset.get('finance_books'):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(finance_book.depreciation_start_date,
row * cint(finance_book.frequency_of_depreciation))
if orginal_schedule_date == posting_date_of_original_invoice:
return True
return False
@property
def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"):

View File

@@ -10,6 +10,8 @@ from frappe.model.dynamic_links import get_dynamic_link_map
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from frappe.model.naming import make_autoname
@@ -1106,6 +1108,45 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(si1.outstanding_amount)
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
def test_gle_made_when_asset_is_returned(self):
create_asset_data()
pi = frappe.new_doc('Purchase Invoice')
pi.supplier = '_Test Supplier'
pi.append('items', {
'item_code': 'Macbook Pro',
'qty': 1
})
pi.set_missing_values()
asset = create_asset(item_code="Macbook Pro")
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
disposal_account = frappe.get_cached_value("Company", "_Test Company", "disposal_account")
# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
loss_for_si = frappe.get_all(
"GL Entry",
filters = {
"voucher_no": si.name,
"account": disposal_account
},
fields = ["credit", "debit"]
)[0]
loss_for_return_si = frappe.get_all(
"GL Entry",
filters = {
"voucher_no": return_si.name,
"account": disposal_account
},
fields = ["credit", "debit"]
)[0]
self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit'])
self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit'])
def test_incoming_rate_for_stand_alone_credit_note(self):
return_si = create_sales_invoice(is_return=1, update_stock=1, qty=-1, rate=90000, incoming_rate=10,
@@ -2108,6 +2149,30 @@ class TestSalesInvoice(unittest.TestCase):
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
enable_discount_accounting(enable=0)
def test_asset_depreciation_on_sale(self):
"""
Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on Sept 30.
"""
create_asset_data()
asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1)
post_depreciation_entries(getdate("2021-09-30"))
create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30"))
asset.load_from_db()
expected_values = [
["2020-06-30", 1311.48, 1311.48],
["2021-06-30", 20000.0, 21311.48],
["2021-09-30", 3966.76, 25278.24]
]
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)
def test_sales_invoice_against_supplier(self):
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import make_customer
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
@@ -2360,6 +2425,7 @@ def create_sales_invoice(**args):
"price_list_rate": args.price_list_rate if args.get("price_list_rate") is not None else 100,
"income_account": args.income_account or "Sales - _TC",
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
"asset": args.asset or None,
"discount_account": args.discount_account or None,
"discount_amount": args.discount_amount or 0,
"cost_center": args.cost_center or "_Test Cost Center - _TC",

View File

@@ -7,12 +7,19 @@
"field_order": [
"activity_type",
"description",
"section_break_3",
"from_time",
"column_break_5",
"to_time",
"section_break_7",
"billing_hours",
"column_break_9",
"billing_amount",
"section_break_11",
"timesheet_detail",
"column_break_5",
"time_sheet",
"project_name",
"timesheet_detail"
"project_name"
],
"fields": [
{
@@ -64,10 +71,40 @@
"label": "Description",
"read_only": 1
},
{
"fieldname": "from_time",
"fieldtype": "Datetime",
"label": "From Time"
},
{
"fieldname": "to_time",
"fieldtype": "Datetime",
"label": "To Time"
},
{
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"label": "Time"
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"label": "Totals"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break",
"label": "Reference"
},
{
"fieldname": "project_name",
"fieldtype": "Data",
@@ -77,7 +114,7 @@
],
"istable": 1,
"links": [],
"modified": "2021-06-08 14:43:02.748981",
"modified": "2021-08-15 18:37:08.084930",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Timesheet",

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Share Transfer", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Share Transfer
() => frappe.tests.make('Share Transfer', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Share Type", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Share Type
() => frappe.tests.make('Share Type', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Shareholder", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Shareholder
() => frappe.tests.make('Shareholder', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Subscription Invoice", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Subscription Invoice
() => frappe.tests.make('Subscription Invoice', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -21,7 +21,7 @@
"column_break_13",
"billing_interval_count",
"payment_plan_section",
"payment_plan_id",
"product_price_id",
"column_break_16",
"payment_gateway",
"accounting_dimensions_section",
@@ -114,11 +114,6 @@
"fieldtype": "Section Break",
"label": "Payment Plan"
},
{
"fieldname": "payment_plan_id",
"fieldtype": "Data",
"label": "Payment Plan"
},
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
@@ -144,10 +139,15 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "product_price_id",
"fieldtype": "Data",
"label": "Product Price ID"
}
],
"links": [],
"modified": "2021-08-09 10:53:44.205774",
"modified": "2021-08-13 10:53:44.205774",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Subscription Plan", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Subscription Plan
() => frappe.tests.make('Subscription Plan', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Subscription Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Subscription Settings
() => frappe.tests.make('Subscription Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Tax Category", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Tax Category
() => frappe.tests.make('Tax Category', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Tax Rule", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Tax Rule
() => frappe.tests.make('Tax Rule', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Tax Withholding Category", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Tax Withholding Category
() => frappe.tests.make('Tax Withholding Category', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1086,3 +1086,14 @@ def get_journal_entry(account, stock_adjustment_account, amount):
db_or_cr_stock_adjustment_account : abs(amount)
}]
}
def check_and_delete_linked_reports(report):
""" Check if reports are referenced in Desktop Icon """
icons = frappe.get_all("Desktop Icon",
fields = ['name'],
filters = {
"_report": report
})
if icons:
for icon in icons:
frappe.delete_doc("Desktop Icon", icon)

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Agriculture Analysis Criteria", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Agriculture Analysis Criteria
() => frappe.tests.make('Agriculture Analysis Criteria', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Agriculture Task", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Agriculture Task
() => frappe.tests.make('Agriculture Task', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Plant Analysis", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Plant Analysis
() => frappe.tests.make('Plant Analysis', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Soil Analysis", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Soil Analysis
() => frappe.tests.make('Soil Analysis', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Weather", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Weather
() => frappe.tests.make('Weather', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -56,12 +56,12 @@ class Asset(AccountsController):
if self.is_existing_asset and self.purchase_invoice:
frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
def prepare_depreciation_data(self):
def prepare_depreciation_data(self, date_of_sale=None):
if self.calculate_depreciation:
self.value_after_depreciation = 0
self.set_depreciation_rate()
self.make_depreciation_schedule()
self.set_accumulated_depreciation()
self.make_depreciation_schedule(date_of_sale)
self.set_accumulated_depreciation(date_of_sale)
else:
self.finance_books = []
self.value_after_depreciation = (flt(self.gross_purchase_amount) -
@@ -167,7 +167,7 @@ class Asset(AccountsController):
d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True),
d.precision("rate_of_depreciation"))
def make_depreciation_schedule(self):
def make_depreciation_schedule(self, date_of_sale):
if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules:
self.schedules = []
@@ -212,6 +212,21 @@ class Asset(AccountsController):
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_sale:
from_date = self.get_from_date(d.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
from_date, date_of_sale)
self.append("schedules", {
"schedule_date": date_of_sale,
"depreciation_amount": depreciation_amount,
"depreciation_method": d.depreciation_method,
"finance_book": d.finance_book,
"finance_book_id": d.idx
})
break
# For first row
if has_pro_rata and n==0:
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
@@ -303,6 +318,21 @@ class Asset(AccountsController):
break
return start
def get_from_date(self, finance_book):
if not self.get('schedules'):
return self.available_for_use_date
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:
from_date = schedule.schedule_date
if from_date:
return from_date
return self.available_for_use_date
# if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
@@ -357,7 +387,7 @@ class Asset(AccountsController):
frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date")
.format(row.idx))
def set_accumulated_depreciation(self, ignore_booked_entry = False):
def set_accumulated_depreciation(self, date_of_sale=None, ignore_booked_entry = False):
straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line']
finance_books = []
@@ -365,7 +395,7 @@ class Asset(AccountsController):
if ignore_booked_entry and d.journal_entry:
continue
if d.finance_book_id not in finance_books:
if int(d.finance_book_id) not in finance_books:
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
finance_books.append(int(d.finance_book_id))
@@ -374,7 +404,7 @@ class Asset(AccountsController):
value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line
if straight_line_idx and i == max(straight_line_idx) - 1:
if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale:
book = self.get('finance_books')[cint(d.finance_book_id) - 1]
depreciation_amount += flt(value_after_depreciation -
flt(book.expected_value_after_useful_life), d.precision("depreciation_amount"))

View File

@@ -176,22 +176,34 @@ def restore_asset(asset_name):
asset.set_status()
@frappe.whitelist()
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
get_asset_details(asset, finance_book)
gl_entries = [
{
"account": fixed_asset_account,
"debit_in_account_currency": asset.gross_purchase_amount,
"debit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center
},
{
"account": accumulated_depr_account,
"credit_in_account_currency": accumulated_depr_amount,
"credit": accumulated_depr_amount,
"cost_center": depreciation_cost_center
}
]
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)
return gl_entries
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset)
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
idx = 1
if finance_book:
for d in asset.finance_books:
if d.finance_book == finance_book:
idx = d.idx
break
value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation
if asset.calculate_depreciation else asset.value_after_depreciation)
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
get_asset_details(asset, finance_book)
gl_entries = [
{
@@ -210,16 +222,37 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
if profit_amount:
debit_or_credit = "debit" if profit_amount < 0 else "credit"
gl_entries.append({
"account": disposal_account,
"cost_center": depreciation_cost_center,
debit_or_credit: abs(profit_amount),
debit_or_credit + "_in_account_currency": abs(profit_amount)
})
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
return gl_entries
def get_asset_details(asset, finance_book=None):
fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset)
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
idx = 1
if finance_book:
for d in asset.finance_books:
if d.finance_book == finance_book:
idx = d.idx
break
value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation
if asset.calculate_depreciation else asset.value_after_depreciation)
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
return fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
debit_or_credit = "debit" if profit_amount < 0 else "credit"
gl_entries.append({
"account": disposal_account,
"cost_center": depreciation_cost_center,
debit_or_credit: abs(profit_amount),
debit_or_credit + "_in_account_currency": abs(profit_amount)
})
@frappe.whitelist()
def get_disposal_account_and_cost_center(company):
disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company,

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Asset", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Asset
() => frappe.tests.make('Asset', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Asset Category", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Asset Category
() => frappe.tests.make('Asset Category', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Asset Maintenance", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Asset Maintenance
() => frappe.tests.make('Asset Maintenance', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Asset Maintenance Log", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Asset Maintenance Log
() => frappe.tests.make('Asset Maintenance Log', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Asset Maintenance Team", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Asset Maintenance Team
() => frappe.tests.make('Asset Maintenance Team', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Asset Movement", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Asset Movement
() => frappe.tests.make('Asset Movement', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Asset Repair", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Asset Repair
() => frappe.tests.make('Asset Repair', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Asset Value Adjustment", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Asset Value Adjustment
() => frappe.tests.make('Asset Value Adjustment', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Location", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Location
() => frappe.tests.make('Location', [
// values to be set
{ location_name: 'Basil Farm' }
]),
() => {
assert.equal(cur_frm.doc.name, 'Basil Farm');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Maintenance Team Member", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Maintenance Team Member
() => frappe.tests.make('Maintenance Team Member', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -24,7 +24,26 @@ frappe.ui.form.on("Supplier", {
}
}
});
frm.set_query("supplier_primary_contact", function(doc) {
return {
query: "erpnext.buying.doctype.supplier.supplier.get_supplier_primary_contact",
filters: {
"supplier": doc.name
}
};
});
frm.set_query("supplier_primary_address", function(doc) {
return {
filters: {
"link_doctype": "Supplier",
"link_name": doc.name
}
};
});
},
refresh: function (frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' }
@@ -78,6 +97,30 @@ frappe.ui.form.on("Supplier", {
});
},
supplier_primary_address: function(frm) {
if (frm.doc.supplier_primary_address) {
frappe.call({
method: 'frappe.contacts.doctype.address.address.get_address_display',
args: {
"address_dict": frm.doc.supplier_primary_address
},
callback: function(r) {
frm.set_value("primary_address", r.message);
}
});
}
if (!frm.doc.supplier_primary_address) {
frm.set_value("primary_address", "");
}
},
supplier_primary_contact: function(frm) {
if (!frm.doc.supplier_primary_contact) {
frm.set_value("mobile_no", "");
frm.set_value("email_id", "");
}
},
is_internal_supplier: function(frm) {
if (frm.doc.is_internal_supplier == 1) {
frm.toggle_reqd("represents_company", true);

View File

@@ -49,6 +49,13 @@
"address_html",
"column_break1",
"contact_html",
"primary_address_and_contact_detail_section",
"supplier_primary_contact",
"mobile_no",
"email_id",
"column_break_44",
"supplier_primary_address",
"primary_address",
"default_payable_accounts",
"accounts",
"default_tax_withholding_config",
@@ -378,6 +385,47 @@
"fieldname": "allow_purchase_invoice_creation_without_purchase_receipt",
"fieldtype": "Check",
"label": "Allow Purchase Invoice Creation Without Purchase Receipt"
},
{
"fieldname": "primary_address_and_contact_detail_section",
"fieldtype": "Section Break",
"label": "Primary Address and Contact Detail"
},
{
"description": "Reselect, if the chosen contact is edited after save",
"fieldname": "supplier_primary_contact",
"fieldtype": "Link",
"label": "Supplier Primary Contact",
"options": "Contact"
},
{
"fetch_from": "supplier_primary_contact.mobile_no",
"fieldname": "mobile_no",
"fieldtype": "Read Only",
"label": "Mobile No"
},
{
"fetch_from": "supplier_primary_contact.email_id",
"fieldname": "email_id",
"fieldtype": "Read Only",
"label": "Email Id"
},
{
"fieldname": "column_break_44",
"fieldtype": "Column Break"
},
{
"fieldname": "primary_address",
"fieldtype": "Text",
"label": "Primary Address",
"read_only": 1
},
{
"description": "Reselect, if the chosen address is edited after save",
"fieldname": "supplier_primary_address",
"fieldtype": "Link",
"label": "Supplier Primary Address",
"options": "Address"
}
],
"icon": "fa fa-user",
@@ -390,7 +438,7 @@
"link_fieldname": "supplier"
}
],
"modified": "2021-05-18 15:10:11.087191",
"modified": "2021-08-27 18:02:44.314077",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",

View File

@@ -42,7 +42,12 @@ class Supplier(TransactionBase):
if not self.naming_series:
self.naming_series = ''
self.create_primary_contact()
self.create_primary_address()
def validate(self):
self.flags.is_new_doc = self.is_new()
# validation for Naming Series mandatory field...
if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series':
if not self.naming_series:
@@ -76,7 +81,39 @@ class Supplier(TransactionBase):
frappe.throw(_("Internal Supplier for company {0} already exists").format(
frappe.bold(self.represents_company)))
def create_primary_contact(self):
from erpnext.selling.doctype.customer.customer import make_contact
if not self.supplier_primary_contact:
if self.mobile_no or self.email_id:
contact = make_contact(self)
self.db_set('supplier_primary_contact', contact.name)
self.db_set('mobile_no', self.mobile_no)
self.db_set('email_id', self.email_id)
def create_primary_address(self):
from erpnext.selling.doctype.customer.customer import make_address
from frappe.contacts.doctype.address.address import get_address_display
if self.flags.is_new_doc and self.get('address_line1'):
address = make_address(self)
address_display = get_address_display(address.name)
self.db_set("supplier_primary_address", address.name)
self.db_set("primary_address", address_display)
def on_trash(self):
if self.supplier_primary_contact:
frappe.db.sql("""
UPDATE `tabSupplier`
SET
supplier_primary_contact=null,
supplier_primary_address=null,
mobile_no=null,
email_id=null,
primary_address=null
WHERE name=%(name)s""", {"name": self.name})
delete_contact_and_address('Supplier', self.name)
def after_rename(self, olddn, newdn, merge=False):
@@ -104,3 +141,21 @@ class Supplier(TransactionBase):
doc.name, args.get('supplier_email_' + str(i)))
except frappe.NameError:
pass
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
supplier = filters.get("supplier")
return frappe.db.sql("""
SELECT
`tabContact`.name from `tabContact`,
`tabDynamic Link`
WHERE
`tabContact`.name = `tabDynamic Link`.parent
and `tabDynamic Link`.link_name = %(supplier)s
and `tabDynamic Link`.link_doctype = 'Supplier'
and `tabContact`.name like %(txt)s
""", {
'supplier': supplier,
'txt': '%%%s%%' % txt
})

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Contract", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Contract
() => frappe.tests.make('Contract', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Contract Fulfilment Checklist", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Contract Fulfilment Checklist
() => frappe.tests.make('Contract Fulfilment Checklist', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Contract Template", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Contract Template
() => frappe.tests.make('Contract Template', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Market Segment", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Market Segment
() => frappe.tests.make('Market Segment', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Opportunity Type", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Opportunity Type
() => frappe.tests.make('Opportunity Type', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Sales Stage", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Sales Stage
() => frappe.tests.make('Sales Stage', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
// Testing Setup Module in Education
QUnit.module('education');
QUnit.test('Test: Academic Year', function(assert){
assert.expect(3);
let done = assert.async();
frappe.run_serially([
() => {
return frappe.tests.make('Academic Year', [
{academic_year_name: '2016-17'},
{year_start_date: '2016-07-20'},
{year_end_date:'2017-06-20'},
]);
},
() => {
assert.ok(cur_frm.doc.academic_year_name=='2016-17');
assert.ok(cur_frm.doc.year_start_date=='2016-07-20');
assert.ok(cur_frm.doc.year_end_date=='2017-06-20');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Article", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Article
() => frappe.tests.make('Article', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Content Question", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Content Question
() => frappe.tests.make('Content Question', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Course Activity", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Course Activity
() => frappe.tests.make('Course Activity', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Course Content", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Course Content
() => frappe.tests.make('Course Content', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Course Enrollment", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Course Enrollment
() => frappe.tests.make('Course Enrollment', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Course Schedule", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Course Schedule
() => frappe.tests.make('Course Schedule', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Course Scheduling Tool", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Course Scheduling Tool
() => frappe.tests.make('Course Scheduling Tool', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Course Topic", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Course Topic
() => frappe.tests.make('Course Topic', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Fee Category", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Fee Category
() => frappe.tests.make('Fee Category', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Fee Schedule", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially('Fee Schedule', [
// insert a new Fee Schedule
() => frappe.tests.make([
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Fee Structure", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Fee Structure
() => frappe.tests.make('Fee Structure', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Program Enrollment", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Program Enrollment
() => frappe.tests.make('Program Enrollment', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Program Enrollment Tool", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Program Enrollment Tool
() => frappe.tests.make('Program Enrollment Tool', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Question", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Question
() => frappe.tests.make('Question', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Quiz", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Quiz
() => frappe.tests.make('Quiz', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Quiz Activity", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Quiz Activity
() => frappe.tests.make('Quiz Activity', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Quiz Result", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Quiz Result
() => frappe.tests.make('Quiz Result', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: School House", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new School House
() => frappe.tests.make('School House', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

Some files were not shown because too many files have changed in this diff Show More