Merge branch 'develop' into v12-lead-address-contact
| @ -1,465 +1,466 @@ | ||||
| { | ||||
|     "country_code": "ae",  | ||||
|     "name": "U.A.E - Chart of Accounts",  | ||||
|     "country_code": "ae", | ||||
|     "name": "U.A.E - Chart of Accounts", | ||||
|     "tree": { | ||||
|         "Assets": { | ||||
|             "Current Assets": { | ||||
|                 "Accounts Receivable": { | ||||
|                     "Corporate Credit Cards": { | ||||
|                         "account_type": "Receivable" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Other Receivable": { | ||||
|                         "Accrued Rebates Due from Suppliers": { | ||||
|                             "account_type": "Receivable" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Accrued Income from Suppliers": { | ||||
|                             "account_type": "Receivable" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Other Debtors": { | ||||
|                             "account_type": "Receivable" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "account_type": "Receivable" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Post Dated Cheques Received": { | ||||
|                         "account_type": "Receivable" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Staff Receivable": { | ||||
|                         "account_type": "Receivable" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Trade Receivable": { | ||||
|                         "account_type": "Receivable" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Trade in Opening Fees": { | ||||
|                         "account_type": "Receivable" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "account_type": "Receivable" | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Cash in Hand & Banks": { | ||||
|                     "Banks": { | ||||
|                         "Bank Margin On LC & LG": {},  | ||||
|                         "Banks Blocked Deposits": {},  | ||||
|                         "Banks Call Deposit Accounts": {},  | ||||
|                         "Bank Margin On LC & LG": {}, | ||||
|                         "Banks Blocked Deposits": {}, | ||||
|                         "Banks Call Deposit Accounts": {}, | ||||
|                         "Banks Current Accounts": { | ||||
|                             "account_type": "Bank" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "account_type": "Bank" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Cash in Hand": { | ||||
|                         "Cash in Safe": { | ||||
|                             "Main Safe": { | ||||
|                                 "account_type": "Cash" | ||||
|                             },  | ||||
|                             }, | ||||
|                             "Main Safe - Foreign Currency": { | ||||
|                                 "account_type": "Cash" | ||||
|                             } | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Petty Cash": { | ||||
|                             "Petty Cash - Administration": { | ||||
|                                 "account_type": "Cash" | ||||
|                             },  | ||||
|                             }, | ||||
|                             "Petty Cash - Others": { | ||||
|                                 "account_type": "Cash" | ||||
|                             } | ||||
|                         },  | ||||
|                         }, | ||||
|                         "account_type": "Cash" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Cash in Transit": { | ||||
|                         "Credit Cards": { | ||||
|                             "Gateway Credit Cards": { | ||||
|                                 "account_type": "Bank" | ||||
|                             },  | ||||
|                             }, | ||||
|                             "Manual Visa & Master Cards": { | ||||
|                                 "account_type": "Bank" | ||||
|                             },  | ||||
|                             }, | ||||
|                             "PayPal Account": { | ||||
|                                 "account_type": "Bank" | ||||
|                             },  | ||||
|                             }, | ||||
|                             "Visa & Master Credit Cards": { | ||||
|                                 "account_type": "Bank" | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Inventory": { | ||||
|                     "Consigned Stock": { | ||||
|                         "Handling Difference in Inventory": { | ||||
|                             "account_type": "Stock Adjustment" | ||||
|                         },  | ||||
|                         "Handling Difference in Inventory": {}, | ||||
|                         "Items Delivered to Customs on temporary Base": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Stock in Hand": { | ||||
|                         "account_type": "Stock" | ||||
|                     } | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Preliminary and Preoperating Expenses": { | ||||
|                     "Preoperating Expenses": {} | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Prepayments & Deposits": { | ||||
|                     "Deposits": { | ||||
|                         "Deposit - Office Rent": {},  | ||||
|                         "Deposit Others": {},  | ||||
|                         "Deposit to Immigration (Visa)": {},  | ||||
|                         "Deposit - Office Rent": {}, | ||||
|                         "Deposit Others": {}, | ||||
|                         "Deposit to Immigration (Visa)": {}, | ||||
|                         "Deposits - Customs": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Prepaid Taxes": { | ||||
|                         "Sales Taxes Receivables": {},  | ||||
|                         "Sales Taxes Receivables": {}, | ||||
|                         "Withholding Tax Receivables": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Prepayments": { | ||||
|                         "Other Prepayments": {},  | ||||
|                         "PrePaid Advertisement Expenses": {},  | ||||
|                         "Prepaid Bank Guarantee": {},  | ||||
|                         "Prepaid Consultancy Fees": {},  | ||||
|                         "Prepaid Employees Housing": {},  | ||||
|                         "Prepaid Finance charge for Loans": {},  | ||||
|                         "Prepaid Legal Fees": {},  | ||||
|                         "Prepaid License Fees": {},  | ||||
|                         "Prepaid Life Insurance": {},  | ||||
|                         "Prepaid Maintenance": {},  | ||||
|                         "Prepaid Medical Insurance": {},  | ||||
|                         "Prepaid Office Rent": {},  | ||||
|                         "Prepaid Other Insurance": {},  | ||||
|                         "Prepaid Schooling Fees": {},  | ||||
|                         "Prepaid Site Hosting Fees": {},  | ||||
|                         "Other Prepayments": {}, | ||||
|                         "PrePaid Advertisement Expenses": {}, | ||||
|                         "Prepaid Bank Guarantee": {}, | ||||
|                         "Prepaid Consultancy Fees": {}, | ||||
|                         "Prepaid Employees Housing": {}, | ||||
|                         "Prepaid Finance charge for Loans": {}, | ||||
|                         "Prepaid Legal Fees": {}, | ||||
|                         "Prepaid License Fees": {}, | ||||
|                         "Prepaid Life Insurance": {}, | ||||
|                         "Prepaid Maintenance": {}, | ||||
|                         "Prepaid Medical Insurance": {}, | ||||
|                         "Prepaid Office Rent": {}, | ||||
|                         "Prepaid Other Insurance": {}, | ||||
|                         "Prepaid Schooling Fees": {}, | ||||
|                         "Prepaid Site Hosting Fees": {}, | ||||
|                         "Prepaid Sponsorship Fees": {} | ||||
|                     } | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "Long Term Assets": { | ||||
|                 "Fixed Assets": { | ||||
|                     "Accumulated Depreciation": { | ||||
|                         "Acc. Depreciation of Motor Vehicles": { | ||||
|                             "account_type": "Accumulated Depreciation" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Acc. Deprn.Computer Hardware & Software": { | ||||
|                             "account_type": "Accumulated Depreciation" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Acc.Deprn.of Furniture & Office Equipment": { | ||||
|                             "account_type": "Accumulated Depreciation" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Amortisation on Leasehold Improvement": { | ||||
|                             "account_type": "Accumulated Depreciation" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "account_type": "Accumulated Depreciation" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Fixed Assets (Cost Price)": { | ||||
|                         "Computer Hardware & Software": { | ||||
|                             "account_type": "Fixed Asset" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Furniture and Equipment": { | ||||
|                             "account_type": "Fixed Asset" | ||||
|                         },  | ||||
|                         "Leasehold Improvement": {},  | ||||
|                         }, | ||||
|                         "Leasehold Improvement": {}, | ||||
|                         "Motor Vehicles": { | ||||
|                             "account_type": "Fixed Asset" | ||||
|                         },  | ||||
|                         "Work In Progress": {},  | ||||
|                         }, | ||||
|                         "Work In Progress": {}, | ||||
|                         "account_type": "Fixed Asset" | ||||
|                     } | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Intangible Assets": { | ||||
|                     "Computer Card Renewal": {},  | ||||
|                     "Disposal of Outlets": {},  | ||||
|                     "Computer Card Renewal": {}, | ||||
|                     "Disposal of Outlets": {}, | ||||
|                     "Registration of Trademarks": {} | ||||
|                 },  | ||||
|                 "Intercompany Accounts": {},  | ||||
|                 }, | ||||
|                 "Intercompany Accounts": {}, | ||||
|                 "Investments": { | ||||
|                     "Investments in Subsidiaries": {} | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "root_type": "Asset" | ||||
|         },  | ||||
|         }, | ||||
|         "Closing And Temporary Accounts": { | ||||
|             "Closing Accounts": { | ||||
|                 "Closing Account": {} | ||||
|             },  | ||||
|             }, | ||||
|             "root_type": "Liability" | ||||
|         },  | ||||
|         }, | ||||
|         "Expenses": { | ||||
|             "Commercial Expenses": { | ||||
|                 "Consultancy Fees": {},  | ||||
|                 "Consultancy Fees": {}, | ||||
|                 "Provision for Doubtful Debts": {} | ||||
|             },  | ||||
|             }, | ||||
|             "Cost of Sale": { | ||||
|                 "Cost Of Goods Sold": { | ||||
|                     "Cost Of Goods Sold I/C Sales": {},  | ||||
|                     "Cost Of Goods Sold I/C Sales": {}, | ||||
|                     "Cost of Goods Sold in Trading": { | ||||
|                         "account_type": "Cost of Goods Sold" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "account_type": "Cost of Goods Sold" | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Expenses Included In Valuation": { | ||||
|                     "account_type": "Expenses Included In Valuation" | ||||
|                 }, | ||||
|                 "Stock Adjustment": { | ||||
|                     "account_type": "Stock Adjustment" | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "Depreciation": { | ||||
|                 "Depreciation & Amortization": { | ||||
|                     "Amortization on Leasehold Improvement": {},  | ||||
|                     "Amortization on Leasehold Improvement": {}, | ||||
|                     "Depreciation Of Computer Hard & Soft": { | ||||
|                         "account_type": "Depreciation" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Depreciation Of Furniture & Office Equipment\n\t\t\t": { | ||||
|                         "account_type": "Depreciation" | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Depreciation Of Motor Vehicles": { | ||||
|                         "account_type": "Depreciation" | ||||
|                     } | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "Direct Expenses": { | ||||
|                 "Financial Charges": { | ||||
|                     "Air Miles Card Charges": {},  | ||||
|                     "Amex Credit Cards Charges": {},  | ||||
|                     "Bank Finance & Loan Charges": {},  | ||||
|                     "Credit Card Charges": {},  | ||||
|                     "Credit Card Swipe Charges": {},  | ||||
|                     "Air Miles Card Charges": {}, | ||||
|                     "Amex Credit Cards Charges": {}, | ||||
|                     "Bank Finance & Loan Charges": {}, | ||||
|                     "Credit Card Charges": {}, | ||||
|                     "Credit Card Swipe Charges": {}, | ||||
|                     "PayPal Charges": {} | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "MISC Charges": { | ||||
|                 "Other Charges": { | ||||
|                     "Capital Loss": { | ||||
|                         "Disposal of Business Branch": {},  | ||||
|                         "Loss On Fixed Assets Disposal": {},  | ||||
|                         "Disposal of Business Branch": {}, | ||||
|                         "Loss On Fixed Assets Disposal": {}, | ||||
|                         "Loss on Difference on Exchange": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Other Non Operating Exp": { | ||||
|                         "Other Non Operating Expenses": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Previous Year Adjustments": { | ||||
|                         "Previous Year Adjustments Account": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Royalty Fees": { | ||||
|                         "Royalty to Parent Co.": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Tax / Zakat Expenses": { | ||||
|                         "Income Tax": { | ||||
|                             "account_type": "Tax" | ||||
|                         },  | ||||
|                         "Zakat": {},  | ||||
|                         }, | ||||
|                         "Zakat": {}, | ||||
|                         "account_type": "Tax" | ||||
|                     } | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "Share Resources": { | ||||
|                 "Share Resource Expenses Account": {} | ||||
|             },  | ||||
|             }, | ||||
|             "Store Operating Expenses": { | ||||
|                 "Selling, General & Admin Expenses": { | ||||
|                     "Advertising Expenses": { | ||||
|                         "Other - Advertising Expenses": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Bank & Finance Charges": { | ||||
|                         "Other Bank Charges": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Communications": { | ||||
|                         "Courier": {},  | ||||
|                         "Others - Communication": {},  | ||||
|                         "Telephone": {},  | ||||
|                         "Courier": {}, | ||||
|                         "Others - Communication": {}, | ||||
|                         "Telephone": {}, | ||||
|                         "Web Site Hosting Fees": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Office & Various Expenses": { | ||||
|                         "Cleaning": {},  | ||||
|                         "Conveyance Expenses": {},  | ||||
|                         "Gifts & Donations": {},  | ||||
|                         "Insurance": {},  | ||||
|                         "Kitchen and Buffet Expenses": {},  | ||||
|                         "Maintenance": {},  | ||||
|                         "Others - Office Various Expenses": {},  | ||||
|                         "Security & Guard": {},  | ||||
|                         "Stationary From Suppliers": {},  | ||||
|                         "Stationary Out Of Stock": {},  | ||||
|                         "Subscriptions": {},  | ||||
|                         "Training": {},  | ||||
|                         "Cleaning": {}, | ||||
|                         "Conveyance Expenses": {}, | ||||
|                         "Gifts & Donations": {}, | ||||
|                         "Insurance": {}, | ||||
|                         "Kitchen and Buffet Expenses": {}, | ||||
|                         "Maintenance": {}, | ||||
|                         "Others - Office Various Expenses": {}, | ||||
|                         "Security & Guard": {}, | ||||
|                         "Stationary From Suppliers": {}, | ||||
|                         "Stationary Out Of Stock": {}, | ||||
|                         "Subscriptions": {}, | ||||
|                         "Training": {}, | ||||
|                         "Vehicle Expenses": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Personnel Cost": { | ||||
|                         "Basic Salary": {},  | ||||
|                         "End Of Service Indemnity": {},  | ||||
|                         "Housing Allowance": {},  | ||||
|                         "Leave Salary": {},  | ||||
|                         "Leave Ticket": {},  | ||||
|                         "Life Insurance": {},  | ||||
|                         "Medical Insurance": {},  | ||||
|                         "Personnel Cost Others": {},  | ||||
|                         "Sales Commission": {},  | ||||
|                         "Staff School Allowances": {},  | ||||
|                         "Transportation Allowance": {},  | ||||
|                         "Uniform": {},  | ||||
|                         "Basic Salary": {}, | ||||
|                         "End Of Service Indemnity": {}, | ||||
|                         "Housing Allowance": {}, | ||||
|                         "Leave Salary": {}, | ||||
|                         "Leave Ticket": {}, | ||||
|                         "Life Insurance": {}, | ||||
|                         "Medical Insurance": {}, | ||||
|                         "Personnel Cost Others": {}, | ||||
|                         "Sales Commission": {}, | ||||
|                         "Staff School Allowances": {}, | ||||
|                         "Transportation Allowance": {}, | ||||
|                         "Uniform": {}, | ||||
|                         "Visa Expenses": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Professional & Legal Fees": { | ||||
|                         "Audit Fees": {},  | ||||
|                         "Legal fees": {},  | ||||
|                         "Others - Professional Fees": {},  | ||||
|                         "Sponsorship Fees": {},  | ||||
|                         "Audit Fees": {}, | ||||
|                         "Legal fees": {}, | ||||
|                         "Others - Professional Fees": {}, | ||||
|                         "Sponsorship Fees": {}, | ||||
|                         "Trade License Fees": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Provision & Write Off": { | ||||
|                         "Amortisation of Preoperating Expenses": {},  | ||||
|                         "Cash Shortage": {},  | ||||
|                         "Others - Provision & Write off": {},  | ||||
|                         "Write Off Inventory": {},  | ||||
|                         "Amortisation of Preoperating Expenses": {}, | ||||
|                         "Cash Shortage": {}, | ||||
|                         "Others - Provision & Write off": {}, | ||||
|                         "Write Off Inventory": {}, | ||||
|                         "Write Off Receivables & Payables": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Rent Expenses": { | ||||
|                         "Office Rent": {},  | ||||
|                         "Office Rent": {}, | ||||
|                         "Warehouse Rent": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Travel Expenses": { | ||||
|                         "Air tickets": {},  | ||||
|                         "Hotel": {},  | ||||
|                         "Meals": {},  | ||||
|                         "Others": {},  | ||||
|                         "Air tickets": {}, | ||||
|                         "Hotel": {}, | ||||
|                         "Meals": {}, | ||||
|                         "Others": {}, | ||||
|                         "Per Diem": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Utilities": { | ||||
|                         "Other Utility Cahrges": {},  | ||||
|                         "Other Utility Cahrges": {}, | ||||
|                         "Water & Electricity": {} | ||||
|                     } | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "root_type": "Expense" | ||||
|         },  | ||||
|         }, | ||||
|         "Liabilities": { | ||||
|             "Current Liabilities": { | ||||
|                 "Accounts Payable": { | ||||
|                     "Payables": { | ||||
|                         "Advance Payable to Suppliers": { | ||||
|                             "account_type": "Payable" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Consigned Payable": { | ||||
|                             "account_type": "Payable" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Other Payable": { | ||||
|                             "account_type": "Payable" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Post Dated Cheques Paid": { | ||||
|                             "account_type": "Payable" | ||||
|                         },  | ||||
|                         "Staff Payable": {},  | ||||
|                         }, | ||||
|                         "Staff Payable": {}, | ||||
|                         "Suppliers Price Protection": { | ||||
|                             "account_type": "Payable" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "Trade Payable": { | ||||
|                             "account_type": "Payable" | ||||
|                         },  | ||||
|                         }, | ||||
|                         "account_type": "Payable" | ||||
|                     } | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Accruals & Provisions": { | ||||
|                     "Accruals": { | ||||
|                         "Accrued Personnel Cost": { | ||||
|                             "Accrued - Commissions": {},  | ||||
|                             "Accrued - Leave Salary": {},  | ||||
|                             "Accrued - Leave Tickets": {},  | ||||
|                             "Accrued - Salaries": {},  | ||||
|                             "Accrued Other Personnel Cost": {},  | ||||
|                             "Accrued Salaries Increment": {},  | ||||
|                             "Accrued - Commissions": {}, | ||||
|                             "Accrued - Leave Salary": {}, | ||||
|                             "Accrued - Leave Tickets": {}, | ||||
|                             "Accrued - Salaries": {}, | ||||
|                             "Accrued Other Personnel Cost": {}, | ||||
|                             "Accrued Salaries Increment": {}, | ||||
|                             "Accrued-Staff Bonus": {} | ||||
|                         } | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Accrued Expenses": { | ||||
|                         "Accrued Other Expenses": { | ||||
|                             "Accrued - Audit Fees": {},  | ||||
|                             "Accrued - Office Rent": {},  | ||||
|                             "Accrued - Sponsorship": {},  | ||||
|                             "Accrued - Telephone": {},  | ||||
|                             "Accrued - Utilities": {},  | ||||
|                             "Accrued - Audit Fees": {}, | ||||
|                             "Accrued - Office Rent": {}, | ||||
|                             "Accrued - Sponsorship": {}, | ||||
|                             "Accrued - Telephone": {}, | ||||
|                             "Accrued - Utilities": {}, | ||||
|                             "Accrued Others": {} | ||||
|                         } | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Other Current Liabilities": { | ||||
|                         "Accrued Dubai Customs": {},  | ||||
|                         "Deferred income": {},  | ||||
|                         "Accrued Dubai Customs": {}, | ||||
|                         "Deferred income": {}, | ||||
|                         "Shipping & Handling": {} | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Provisions": { | ||||
|                         "Tax Payables": { | ||||
|                             "Income Tax Payable": {},  | ||||
|                             "Sales Tax Payable": {},  | ||||
|                             "Income Tax Payable": {}, | ||||
|                             "Sales Tax Payable": {}, | ||||
|                             "Withholding Tax Payable": {} | ||||
|                         } | ||||
|                     },  | ||||
|                     }, | ||||
|                     "Short Term Loan": {} | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Duties and Taxes": { | ||||
|                     "account_type": "Tax",  | ||||
|                     "account_type": "Tax", | ||||
|                     "is_group": 1 | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Reservations & Credit Notes": { | ||||
|                     "Credit Notes": { | ||||
|                         "Credit Notes to Customers": {},  | ||||
|                         "Credit Notes to Customers": {}, | ||||
|                         "Reservations": {} | ||||
|                     } | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Stock Liabilities": { | ||||
|                     "Stock Received But Not Billed": { | ||||
|                         "account_type": "Stock Received But Not Billed" | ||||
|                     } | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Unearned Income": {} | ||||
|             },  | ||||
|             }, | ||||
|             "Long Term Liabilities": { | ||||
|                 "Long Term Loans & Provisions": {} | ||||
|             },  | ||||
|             }, | ||||
|             "root_type": "Liability" | ||||
|         },  | ||||
|         }, | ||||
|         "Revenue": { | ||||
|             "Direct Revenue": { | ||||
|                 "Other Direct Revenue": { | ||||
|                     "Other Revenue - Operating": { | ||||
|                         "Advertising Income": {},  | ||||
|                         "Branding Income": {},  | ||||
|                         "Early Setmt Margin from Suppliers": {},  | ||||
|                         "Marketing Rebate from Suppliers": {},  | ||||
|                         "Rebate from Suppliers": {},  | ||||
|                         "Service Income": {},  | ||||
|                         "Advertising Income": {}, | ||||
|                         "Branding Income": {}, | ||||
|                         "Early Setmt Margin from Suppliers": {}, | ||||
|                         "Marketing Rebate from Suppliers": {}, | ||||
|                         "Rebate from Suppliers": {}, | ||||
|                         "Service Income": {}, | ||||
|                         "Space Rental Income": {} | ||||
|                     } | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "Indirect Revenue": { | ||||
|                 "Other Indirect Revenue": { | ||||
|                     "Capital Gain": {},  | ||||
|                     "Excess In Till": {},  | ||||
|                     "Gain On Difference Of Exchange": {},  | ||||
|                     "Management Consultancy Fees": {},  | ||||
|                     "Capital Gain": {}, | ||||
|                     "Excess In Till": {}, | ||||
|                     "Gain On Difference Of Exchange": {}, | ||||
|                     "Management Consultancy Fees": {}, | ||||
|                     "Other Income": {} | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Other Revenue - Non Operating": { | ||||
|                     "Interest Revenue": {},  | ||||
|                     "Interest from FD": {},  | ||||
|                     "Products Listing Fees from Suppliers": {},  | ||||
|                     "Interest Revenue": {}, | ||||
|                     "Interest from FD": {}, | ||||
|                     "Products Listing Fees from Suppliers": {}, | ||||
|                     "Trade Opening Fees from suppliers": {} | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "Sales": { | ||||
|                 "Sales from Other Regions": { | ||||
|                     "Sales from Other Region": {} | ||||
|                 },  | ||||
|                 }, | ||||
|                 "Sales of same region": { | ||||
|                     "Management Consultancy Fees 1": {},  | ||||
|                     "Sales Account": {},  | ||||
|                     "Management Consultancy Fees 1": {}, | ||||
|                     "Sales Account": {}, | ||||
|                     "Sales of I/C": {} | ||||
|                 } | ||||
|             },  | ||||
|             }, | ||||
|             "root_type": "Income" | ||||
|         },  | ||||
|         }, | ||||
|         "Share Holder Equity": { | ||||
|             "Capital": { | ||||
|                 "Contributed Capital": {},  | ||||
|                 "Share Capital": {},  | ||||
|                 "Shareholders Current A/c": {},  | ||||
|                 "Sub Ordinated Loan": {},  | ||||
|                 "Contributed Capital": {}, | ||||
|                 "Share Capital": {}, | ||||
|                 "Shareholders Current A/c": {}, | ||||
|                 "Sub Ordinated Loan": {}, | ||||
|                 "Treasury Stocks": {} | ||||
|             },  | ||||
|             }, | ||||
|             "Retained Earnings": { | ||||
|                 "Current Year Results": {},  | ||||
|                 "Dividends Paid": {},  | ||||
|                 "Current Year Results": {}, | ||||
|                 "Dividends Paid": {}, | ||||
|                 "Previous Years Results": {} | ||||
|             },  | ||||
|             "account_type": "Equity",  | ||||
|             }, | ||||
|             "account_type": "Equity", | ||||
|             "root_type": "Equity" | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -185,7 +185,8 @@ def validate_account_types(accounts): | ||||
| 		return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)) | ||||
| 
 | ||||
| 	account_types_for_group = ["Bank", "Cash", "Stock"] | ||||
| 	account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] not in ('', 1)] | ||||
| 	# fix logic bug | ||||
| 	account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1] | ||||
| 
 | ||||
| 	missing = list(set(account_types_for_group) - set(account_groups)) | ||||
| 	if missing: | ||||
|  | ||||
| @ -18,6 +18,7 @@ class CostCenter(NestedSet): | ||||
| 
 | ||||
| 	def validate(self): | ||||
| 		self.validate_mandatory() | ||||
| 		self.validate_parent_cost_center() | ||||
| 
 | ||||
| 	def validate_mandatory(self): | ||||
| 		if self.cost_center_name != self.company and not self.parent_cost_center: | ||||
| @ -25,6 +26,12 @@ class CostCenter(NestedSet): | ||||
| 		elif self.cost_center_name == self.company and self.parent_cost_center: | ||||
| 			frappe.throw(_("Root cannot have a parent cost center")) | ||||
| 
 | ||||
| 	def validate_parent_cost_center(self): | ||||
| 		if self.parent_cost_center: | ||||
| 			if not frappe.db.get_value('Cost Center', self.parent_cost_center, 'is_group'): | ||||
| 				frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format( | ||||
| 					frappe.bold(self.parent_cost_center))) | ||||
| 
 | ||||
| 	def convert_group_to_ledger(self): | ||||
| 		if self.check_if_child_exists(): | ||||
| 			frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes")) | ||||
|  | ||||
| @ -1,12 +1,26 @@ | ||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # License: GNU General Public License v3. See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| 
 | ||||
| import unittest | ||||
| import frappe | ||||
| 
 | ||||
| test_records = frappe.get_test_records('Cost Center') | ||||
| 
 | ||||
| class TestCostCenter(unittest.TestCase): | ||||
| 	def test_cost_center_creation_against_child_node(self): | ||||
| 
 | ||||
| 		if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}): | ||||
| 			frappe.get_doc(test_records[1]).insert() | ||||
| 
 | ||||
| 		cost_center = frappe.get_doc({ | ||||
| 			'doctype': 'Cost Center', | ||||
| 			'cost_center_name': '_Test Cost Center 3', | ||||
| 			'parent_cost_center': '_Test Cost Center 2 - _TC', | ||||
| 			'is_group': 0, | ||||
| 			'company': '_Test Company' | ||||
| 		}) | ||||
| 
 | ||||
| 		self.assertRaises(frappe.ValidationError, cost_center.save) | ||||
| 
 | ||||
| def create_cost_center(**args): | ||||
| 	args = frappe._dict(args) | ||||
|  | ||||
| @ -350,13 +350,13 @@ def get_amount(ref_doc): | ||||
| 	if dt in ["Sales Order", "Purchase Order"]: | ||||
| 		grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid) | ||||
| 
 | ||||
| 	if dt in ["Sales Invoice", "Purchase Invoice"]: | ||||
| 	elif dt in ["Sales Invoice", "Purchase Invoice"]: | ||||
| 		if ref_doc.party_account_currency == ref_doc.currency: | ||||
| 			grand_total = flt(ref_doc.outstanding_amount) | ||||
| 		else: | ||||
| 			grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate | ||||
| 
 | ||||
| 	if dt == "Fees": | ||||
| 	elif dt == "Fees": | ||||
| 		grand_total = ref_doc.outstanding_amount | ||||
| 
 | ||||
| 	if grand_total > 0 : | ||||
|  | ||||
| @ -34,8 +34,7 @@ class PricingRule(Document): | ||||
| 
 | ||||
| 	def validate_duplicate_apply_on(self): | ||||
| 		field = apply_on_dict.get(self.apply_on) | ||||
| 		values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field)] | ||||
| 
 | ||||
| 		values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field] | ||||
| 		if len(values) != len(set(values)): | ||||
| 			frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on)) | ||||
| 
 | ||||
|  | ||||
| @ -330,23 +330,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ | ||||
| 			frm: cur_frm | ||||
| 		}) | ||||
| 	}, | ||||
| 
 | ||||
| 	item_code: function(frm, cdt, cdn) { | ||||
| 		var row = locals[cdt][cdn]; | ||||
| 		if(row.item_code) { | ||||
| 			frappe.call({ | ||||
| 				method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account", | ||||
| 				args: { | ||||
| 					"item": row.item_code, | ||||
| 					"fieldname": "fixed_asset_account", | ||||
| 					"company": frm.doc.company | ||||
| 				}, | ||||
| 				callback: function(r, rt) { | ||||
| 					frappe.model.set_value(cdt, cdn, "expense_account", r.message); | ||||
| 				} | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice); | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "hash", | ||||
|  "creation": "2013-05-22 12:43:10", | ||||
|  "doctype": "DocType", | ||||
| @ -507,7 +508,8 @@ | ||||
|    "depends_on": "enable_deferred_expense", | ||||
|    "fieldname": "service_stop_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service Stop Date" | ||||
|    "label": "Service Stop Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
| @ -523,13 +525,15 @@ | ||||
|    "depends_on": "enable_deferred_expense", | ||||
|    "fieldname": "service_start_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service Start Date" | ||||
|    "label": "Service Start Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "enable_deferred_expense", | ||||
|    "fieldname": "service_end_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service End Date" | ||||
|    "label": "Service End Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "reference", | ||||
| @ -766,7 +770,8 @@ | ||||
|  ], | ||||
|  "idx": 1, | ||||
|  "istable": 1, | ||||
|  "modified": "2019-11-21 16:27:52.043744", | ||||
|  "links": [], | ||||
|  "modified": "2019-12-04 12:23:17.046413", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Purchase Invoice Item", | ||||
|  | ||||
| @ -789,22 +789,21 @@ frappe.ui.form.on('Sales Invoice', { | ||||
| 					method: "frappe.client.get_value", | ||||
| 					args:{ | ||||
| 						doctype: "Patient", | ||||
| 						filters: {"name": frm.doc.patient}, | ||||
| 						filters: { | ||||
| 							"name": frm.doc.patient | ||||
| 						}, | ||||
| 						fieldname: "customer" | ||||
| 					}, | ||||
| 					callback:function(patient_customer) { | ||||
| 						if(patient_customer){ | ||||
| 							frm.set_value("customer", patient_customer.message.customer); | ||||
| 							frm.refresh_fields(); | ||||
| 					callback:function(r) { | ||||
| 						if(r && r.message.customer){ | ||||
| 							frm.set_value("customer", r.message.customer); | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
| 			else{ | ||||
| 					frm.set_value("customer", ''); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	refresh: function(frm) { | ||||
| 		if (frappe.boot.active_domains.includes("Healthcare")){ | ||||
| 			frm.set_df_property("patient", "hidden", 0); | ||||
|  | ||||
| @ -538,7 +538,7 @@ class SalesInvoice(SellingController): | ||||
| 					is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item') | ||||
| 					if  (d.item_code and is_stock_item == 1\ | ||||
| 						and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])): | ||||
| 						msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1) | ||||
| 						msgprint(_("{0} is mandatory for Stock Item {1}").format(i,d.item_code), raise_exception=1) | ||||
| 
 | ||||
| 
 | ||||
| 	def validate_proj_cust(self): | ||||
| @ -1048,13 +1048,18 @@ class SalesInvoice(SellingController): | ||||
| 				continue | ||||
| 
 | ||||
| 			for serial_no in item.serial_no.split("\n"): | ||||
| 				sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no, | ||||
| 					["sales_invoice", "item_code"]) | ||||
| 				if sales_invoice and item_code == item.item_code and self.name != sales_invoice: | ||||
| 					sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") | ||||
| 				serial_no_details = frappe.db.get_value("Serial No", serial_no, | ||||
| 					["sales_invoice", "item_code"], as_dict=1) | ||||
| 
 | ||||
| 				if not serial_no_details: | ||||
| 					continue | ||||
| 
 | ||||
| 				if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \ | ||||
| 					and self.name != serial_no_details.sales_invoice: | ||||
| 					sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company") | ||||
| 					if sales_invoice_company == self.company: | ||||
| 						frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" | ||||
| 							.format(serial_no, sales_invoice))) | ||||
| 							.format(serial_no, serial_no_details.sales_invoice))) | ||||
| 
 | ||||
| 	def update_project(self): | ||||
| 		if self.project: | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "hash", | ||||
|  "creation": "2013-06-04 11:02:19", | ||||
|  "doctype": "DocType", | ||||
| @ -484,7 +485,8 @@ | ||||
|    "depends_on": "enable_deferred_revenue", | ||||
|    "fieldname": "service_stop_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service Stop Date" | ||||
|    "label": "Service Stop Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
| @ -500,13 +502,15 @@ | ||||
|    "depends_on": "enable_deferred_revenue", | ||||
|    "fieldname": "service_start_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service Start Date" | ||||
|    "label": "Service Start Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "enable_deferred_revenue", | ||||
|    "fieldname": "service_end_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Service End Date" | ||||
|    "label": "Service End Date", | ||||
|    "no_copy": 1 | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
| @ -783,7 +787,8 @@ | ||||
|  ], | ||||
|  "idx": 1, | ||||
|  "istable": 1, | ||||
|  "modified": "2019-07-16 16:36:46.527606", | ||||
|  "links": [], | ||||
|  "modified": "2019-12-04 12:22:38.517710", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Sales Invoice Item", | ||||
|  | ||||
| @ -70,7 +70,7 @@ class ShippingRule(Document): | ||||
| 
 | ||||
| 	def get_shipping_amount_from_rules(self, value): | ||||
| 		for condition in self.get("conditions"): | ||||
| 			if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)): | ||||
| 			if not condition.to_value or (flt(condition.from_value) <= flt(value) <= flt(condition.to_value)): | ||||
| 				return condition.shipping_amount | ||||
| 
 | ||||
| 		return 0.0 | ||||
|  | ||||
| @ -162,33 +162,34 @@ def validate_account_for_perpetual_inventory(gl_map): | ||||
| 					frappe.throw(_("Account: {0} can only be updated via Stock Transactions") | ||||
| 						.format(account), StockAccountInvalidTransaction) | ||||
| 
 | ||||
| 			elif account_bal != stock_bal: | ||||
| 				precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | ||||
| 					currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | ||||
| 			# This has been comment for a temporary, will add this code again on release of immutable ledger | ||||
| 			# elif account_bal != stock_bal: | ||||
| 			# 	precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | ||||
| 			# 		currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | ||||
| 
 | ||||
| 				diff = flt(stock_bal - account_bal, precision) | ||||
| 				error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( | ||||
| 					stock_bal, account_bal, frappe.bold(account)) | ||||
| 				error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) | ||||
| 				stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") | ||||
| 			# 	diff = flt(stock_bal - account_bal, precision) | ||||
| 			# 	error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( | ||||
| 			# 		stock_bal, account_bal, frappe.bold(account)) | ||||
| 			# 	error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) | ||||
| 			# 	stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") | ||||
| 
 | ||||
| 				db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') | ||||
| 				db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') | ||||
| 			# 	db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') | ||||
| 			# 	db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') | ||||
| 
 | ||||
| 				journal_entry_args = { | ||||
| 				'accounts':[ | ||||
| 					{'account': account, db_or_cr_warehouse_account : abs(diff)}, | ||||
| 					{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] | ||||
| 				} | ||||
| 			# 	journal_entry_args = { | ||||
| 			# 	'accounts':[ | ||||
| 			# 		{'account': account, db_or_cr_warehouse_account : abs(diff)}, | ||||
| 			# 		{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] | ||||
| 			# 	} | ||||
| 
 | ||||
| 				frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution), | ||||
| 					raise_exception=StockValueAndAccountBalanceOutOfSync, | ||||
| 					title=_('Values Out Of Sync'), | ||||
| 					primary_action={ | ||||
| 						'label': _('Make Journal Entry'), | ||||
| 						'client_action': 'erpnext.route_to_adjustment_jv', | ||||
| 						'args': journal_entry_args | ||||
| 					}) | ||||
| 			# 	frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution), | ||||
| 			# 		raise_exception=StockValueAndAccountBalanceOutOfSync, | ||||
| 			# 		title=_('Values Out Of Sync'), | ||||
| 			# 		primary_action={ | ||||
| 			# 			'label': _('Make Journal Entry'), | ||||
| 			# 			'client_action': 'erpnext.route_to_adjustment_jv', | ||||
| 			# 			'args': journal_entry_args | ||||
| 			# 		}) | ||||
| 
 | ||||
| def validate_cwip_accounts(gl_map): | ||||
| 	cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) | ||||
|  | ||||
| @ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = { | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Supplier Group" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"based_on_payment_terms", | ||||
| 			"label": __("Based On Payment Terms"), | ||||
| 			"fieldtype": "Check", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"tax_id", | ||||
| 			"label": __("Tax Id"), | ||||
|  | ||||
| @ -88,6 +88,11 @@ frappe.query_reports["Accounts Payable Summary"] = { | ||||
| 			"label": __("Supplier Group"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Supplier Group" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"based_on_payment_terms", | ||||
| 			"label": __("Based On Payment Terms"), | ||||
| 			"fieldtype": "Check", | ||||
| 		} | ||||
| 	], | ||||
| 
 | ||||
|  | ||||
| @ -60,6 +60,7 @@ class ReceivablePayableReport(object): | ||||
| 
 | ||||
| 	def get_data(self): | ||||
| 		self.get_gl_entries() | ||||
| 		self.get_sales_invoices_or_customers_based_on_sales_person() | ||||
| 		self.voucher_balance = OrderedDict() | ||||
| 		self.init_voucher_balance() # invoiced, paid, credit_note, outstanding | ||||
| 
 | ||||
| @ -103,12 +104,18 @@ class ReceivablePayableReport(object): | ||||
| 
 | ||||
| 	def get_invoices(self, gle): | ||||
| 		if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'): | ||||
| 			self.invoices.add(gle.voucher_no) | ||||
| 			if self.filters.get("sales_person"): | ||||
| 				if gle.voucher_no in self.sales_person_records.get("Sales Invoice", []) \ | ||||
| 					or gle.party in self.sales_person_records.get("Customer", []): | ||||
| 						self.invoices.add(gle.voucher_no) | ||||
| 			else: | ||||
| 				self.invoices.add(gle.voucher_no) | ||||
| 
 | ||||
| 	def update_voucher_balance(self, gle): | ||||
| 		# get the row where this balance needs to be updated | ||||
| 		# if its a payment, it will return the linked invoice or will be considered as advance | ||||
| 		row = self.get_voucher_balance(gle) | ||||
| 		if not row: return | ||||
| 		# gle_balance will be the total "debit - credit" for receivable type reports and | ||||
| 		# and vice-versa for payable type reports | ||||
| 		gle_balance = self.get_gle_balance(gle) | ||||
| @ -129,8 +136,13 @@ class ReceivablePayableReport(object): | ||||
| 				row.paid -= gle_balance | ||||
| 
 | ||||
| 	def get_voucher_balance(self, gle): | ||||
| 		voucher_balance = None | ||||
| 		if self.filters.get("sales_person"): | ||||
| 			against_voucher = gle.against_voucher or gle.voucher_no | ||||
| 			if not (gle.party in self.sales_person_records.get("Customer", []) or \ | ||||
| 				against_voucher in self.sales_person_records.get("Sales Invoice", [])): | ||||
| 					return | ||||
| 
 | ||||
| 		voucher_balance = None | ||||
| 		if gle.against_voucher: | ||||
| 			# find invoice | ||||
| 			against_voucher = gle.against_voucher | ||||
| @ -318,7 +330,7 @@ class ReceivablePayableReport(object): | ||||
| 			self.append_payment_term(row, d, term) | ||||
| 
 | ||||
| 	def append_payment_term(self, row, d, term): | ||||
| 		if self.filters.get("customer") and d.currency == d.party_account_currency: | ||||
| 		if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency: | ||||
| 			invoiced = d.payment_amount | ||||
| 		else: | ||||
| 			invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) | ||||
| @ -512,6 +524,22 @@ class ReceivablePayableReport(object): | ||||
| 			order by posting_date, party""" | ||||
| 			.format(select_fields, conditions), values, as_dict=True) | ||||
| 
 | ||||
| 	def get_sales_invoices_or_customers_based_on_sales_person(self): | ||||
| 		if self.filters.get("sales_person"): | ||||
| 			lft, rgt = frappe.db.get_value("Sales Person", | ||||
| 				self.filters.get("sales_person"), ["lft", "rgt"]) | ||||
| 
 | ||||
| 			records = frappe.db.sql(""" | ||||
| 				select distinct parent, parenttype | ||||
| 				from `tabSales Team` steam | ||||
| 				where parenttype in ('Customer', 'Sales Invoice') | ||||
| 					and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person) | ||||
| 			""", (lft, rgt), as_dict=1) | ||||
| 
 | ||||
| 			self.sales_person_records = frappe._dict() | ||||
| 			for d in records: | ||||
| 				self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent) | ||||
| 
 | ||||
| 	def prepare_conditions(self): | ||||
| 		conditions = [""] | ||||
| 		values = [self.party_type, self.filters.report_date] | ||||
| @ -564,16 +592,6 @@ class ReceivablePayableReport(object): | ||||
| 			conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)") | ||||
| 			values.append(self.filters.get("sales_partner")) | ||||
| 
 | ||||
| 		if self.filters.get("sales_person"): | ||||
| 			lft, rgt = frappe.db.get_value("Sales Person", | ||||
| 				self.filters.get("sales_person"), ["lft", "rgt"]) | ||||
| 
 | ||||
| 			conditions.append("""exists(select name from `tabSales Team` steam where | ||||
| 				steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1}) | ||||
| 				and ((steam.parent = voucher_no and steam.parenttype = voucher_type) | ||||
| 					or (steam.parent = against_voucher and steam.parenttype = against_voucher_type) | ||||
| 					or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt)) | ||||
| 
 | ||||
| 	def add_supplier_filters(self, conditions, values): | ||||
| 		if self.filters.get("supplier_group"): | ||||
| 			conditions.append("""party in (select name from tabSupplier | ||||
|  | ||||
| @ -106,6 +106,11 @@ frappe.query_reports["Accounts Receivable Summary"] = { | ||||
| 			"label": __("Sales Person"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Sales Person" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"based_on_payment_terms", | ||||
| 			"label": __("Based On Payment Terms"), | ||||
| 			"fieldtype": "Check", | ||||
| 		} | ||||
| 	], | ||||
| 
 | ||||
|  | ||||
| @ -36,7 +36,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): | ||||
| 			self.filters.report_date) or {} | ||||
| 
 | ||||
| 		for party, party_dict in iteritems(self.party_total): | ||||
| 			if party_dict.outstanding <= 0: | ||||
| 			if party_dict.outstanding == 0: | ||||
| 				continue | ||||
| 
 | ||||
| 			row = frappe._dict() | ||||
|  | ||||
| @ -18,14 +18,17 @@ def execute(filters=None): | ||||
| 	return columns, data | ||||
| 
 | ||||
| def get_data(filters, show_party_name): | ||||
| 	party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type'))) | ||||
| 	if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'): | ||||
| 		party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type'))) | ||||
| 	if filters.get('party_type') == 'Student': | ||||
| 		party_name_field = 'first_name' | ||||
| 	elif filters.get('party_type') == 'Shareholder': | ||||
| 		party_name_field = 'title' | ||||
| 	else: | ||||
| 		party_name_field = 'name' | ||||
| 
 | ||||
| 	party_filters = {"name": filters.get("party")} if filters.get("party") else {} | ||||
| 	parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],  | ||||
| 	parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field], | ||||
| 		filters = party_filters, order_by="name") | ||||
| 	company_currency = frappe.get_cached_value('Company',  filters.company,  "default_currency") | ||||
| 	opening_balances = get_opening_balances(filters) | ||||
| @ -70,7 +73,7 @@ def get_data(filters, show_party_name): | ||||
| 		# totals | ||||
| 		for col in total_row: | ||||
| 			total_row[col] += row.get(col) | ||||
| 		 | ||||
| 
 | ||||
| 		row.update({ | ||||
| 			"currency": company_currency | ||||
| 		}) | ||||
| @ -78,7 +81,7 @@ def get_data(filters, show_party_name): | ||||
| 		has_value = False | ||||
| 		if (opening_debit or opening_credit or debit or credit or closing_debit or closing_credit): | ||||
| 			has_value  =True | ||||
| 		 | ||||
| 
 | ||||
| 		if cint(filters.show_zero_values) or has_value: | ||||
| 			data.append(row) | ||||
| 
 | ||||
| @ -94,9 +97,9 @@ def get_data(filters, show_party_name): | ||||
| 
 | ||||
| def get_opening_balances(filters): | ||||
| 	gle = frappe.db.sql(""" | ||||
| 		select party, sum(debit) as opening_debit, sum(credit) as opening_credit  | ||||
| 		select party, sum(debit) as opening_debit, sum(credit) as opening_credit | ||||
| 		from `tabGL Entry` | ||||
| 		where company=%(company)s  | ||||
| 		where company=%(company)s | ||||
| 			and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' | ||||
| 			and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') | ||||
| 		group by party""", { | ||||
| @ -114,11 +117,11 @@ def get_opening_balances(filters): | ||||
| 
 | ||||
| def get_balances_within_period(filters): | ||||
| 	gle = frappe.db.sql(""" | ||||
| 		select party, sum(debit) as debit, sum(credit) as credit  | ||||
| 		select party, sum(debit) as debit, sum(credit) as credit | ||||
| 		from `tabGL Entry` | ||||
| 		where company=%(company)s  | ||||
| 		where company=%(company)s | ||||
| 			and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' | ||||
| 			and posting_date >= %(from_date)s and posting_date <= %(to_date)s  | ||||
| 			and posting_date >= %(from_date)s and posting_date <= %(to_date)s | ||||
| 			and ifnull(is_opening, 'No') = 'No' | ||||
| 		group by party""", { | ||||
| 			"company": filters.company, | ||||
|  | ||||
| @ -569,7 +569,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None) | ||||
| 
 | ||||
| 	warehouse_account = get_warehouse_account_map(company) | ||||
| 
 | ||||
| 	account_balance = get_balance_on(account, posting_date, in_account_currency=False) | ||||
| 	account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True) | ||||
| 
 | ||||
| 	related_warehouses = [wh for wh, wh_details in warehouse_account.items() | ||||
| 		if wh_details.account == account and not wh_details.is_group] | ||||
|  | ||||
| @ -517,15 +517,18 @@ def update_maintenance_status(): | ||||
| 			asset.set_status('Out of Order') | ||||
| 
 | ||||
| def make_post_gl_entry(): | ||||
| 	if not is_cwip_accounting_enabled(self.asset_category): | ||||
| 		return | ||||
| 
 | ||||
| 	assets = frappe.db.sql_list(""" select name from `tabAsset` | ||||
| 		where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate()) | ||||
| 	asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting']) | ||||
| 
 | ||||
| 	for asset in assets: | ||||
| 		doc = frappe.get_doc('Asset', asset) | ||||
| 		doc.make_gl_entries() | ||||
| 	for asset_category in asset_categories: | ||||
| 		if cint(asset_category.enable_cwip_accounting): | ||||
| 			assets = frappe.db.sql_list(""" select name from `tabAsset` | ||||
| 				where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0 | ||||
| 				and available_for_use_date = %s""", (asset_category.name, nowdate())) | ||||
| 
 | ||||
| 			for asset in assets: | ||||
| 				doc = frappe.get_doc('Asset', asset) | ||||
| 				doc.make_gl_entries() | ||||
| 
 | ||||
| def get_asset_naming_series(): | ||||
| 	meta = frappe.get_meta('Asset') | ||||
|  | ||||
| @ -29,7 +29,8 @@ def get_asset_category_account(fieldname, item=None, asset=None, account=None, a | ||||
| 				account=None | ||||
| 
 | ||||
| 		if not account: | ||||
| 			asset_category, company = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) | ||||
| 			asset_details = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) | ||||
| 			asset_category, company = asset_details or [None, None] | ||||
| 
 | ||||
| 	account = frappe.db.get_value("Asset Category Account", | ||||
| 		filters={"parent": asset_category, "company_name": company}, fieldname=fieldname) | ||||
|  | ||||
| @ -26,5 +26,11 @@ frappe.query_reports["Fixed Asset Register"] = { | ||||
| 			fieldtype: "Link", | ||||
| 			options: "Finance Book" | ||||
| 		}, | ||||
| 		{ | ||||
| 			fieldname:"date", | ||||
| 			label: __("Date"), | ||||
| 			fieldtype: "Date", | ||||
| 			default: frappe.datetime.get_today() | ||||
| 		}, | ||||
| 	] | ||||
| }; | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils import cstr | ||||
| from frappe.utils import cstr, today, flt | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| 	filters = frappe._dict(filters or {}) | ||||
| @ -86,8 +86,8 @@ def get_columns(filters): | ||||
| 			"width": 90 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Current Value"), | ||||
| 			"fieldname": "current_value", | ||||
| 			"label": _("Asset Value"), | ||||
| 			"fieldname": "asset_value", | ||||
| 			"options": "Currency", | ||||
| 			"width": 90 | ||||
| 		}, | ||||
| @ -114,7 +114,7 @@ def get_data(filters): | ||||
| 	data = [] | ||||
| 
 | ||||
| 	conditions = get_conditions(filters) | ||||
| 	current_value_map = get_finance_book_value_map(filters.finance_book) | ||||
| 	depreciation_amount_map = get_finance_book_value_map(filters.date, filters.finance_book) | ||||
| 	pr_supplier_map = get_purchase_receipt_supplier_map() | ||||
| 	pi_supplier_map = get_purchase_invoice_supplier_map() | ||||
| 
 | ||||
| @ -125,7 +125,9 @@ def get_data(filters): | ||||
| 			"available_for_use_date", "status", "purchase_invoice"]) | ||||
| 
 | ||||
| 	for asset in assets_record: | ||||
| 		if current_value_map.get(asset.name) is not None: | ||||
| 		asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \ | ||||
| 			- flt(depreciation_amount_map.get(asset.name)) | ||||
| 		if asset_value: | ||||
| 			row = { | ||||
| 				"asset_id": asset.name, | ||||
| 				"asset_name": asset.asset_name, | ||||
| @ -138,19 +140,24 @@ def get_data(filters): | ||||
| 				"location": asset.location, | ||||
| 				"asset_category": asset.asset_category, | ||||
| 				"purchase_date": asset.purchase_date, | ||||
| 				"current_value": current_value_map.get(asset.name) | ||||
| 				"asset_value": asset_value | ||||
| 			} | ||||
| 			data.append(row) | ||||
| 
 | ||||
| 	return data | ||||
| 
 | ||||
| def get_finance_book_value_map(finance_book=''): | ||||
| def get_finance_book_value_map(date, finance_book=''): | ||||
| 	if not date: | ||||
| 		date = today() | ||||
| 	return frappe._dict(frappe.db.sql(''' Select | ||||
| 		parent, value_after_depreciation | ||||
| 		FROM `tabAsset Finance Book` | ||||
| 		parent, SUM(depreciation_amount) | ||||
| 		FROM `tabDepreciation Schedule` | ||||
| 		WHERE | ||||
| 			parentfield='finance_books' | ||||
| 			AND ifnull(finance_book, '')=%s''', cstr(finance_book))) | ||||
| 			parentfield='schedules' | ||||
| 			AND schedule_date<=%s | ||||
| 			AND journal_entry IS NOT NULL | ||||
| 			AND ifnull(finance_book, '')=%s | ||||
| 		GROUP BY parent''', (date, cstr(finance_book)))) | ||||
| 
 | ||||
| def get_purchase_receipt_supplier_map(): | ||||
| 	return frappe._dict(frappe.db.sql(''' Select | ||||
|  | ||||
| @ -18,6 +18,7 @@ frappe.ui.form.on("Purchase Order", { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					"company": frm.doc.company, | ||||
| 					"name": ['!=', frm.doc.supplier_warehouse], | ||||
| 					"is_group": 0 | ||||
| 				} | ||||
| 			} | ||||
| @ -283,6 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( | ||||
| 			}) | ||||
| 		} | ||||
| 
 | ||||
| 		me.dialog.get_field('sub_con_rm_items').check_all_rows() | ||||
| 
 | ||||
| 		me.dialog.show() | ||||
| 		this.dialog.set_primary_action(__('Transfer'), function() { | ||||
| 			me.values = me.dialog.get_values(); | ||||
|  | ||||
| @ -17,6 +17,8 @@ from erpnext.stock.doctype.material_request.material_request import make_purchas | ||||
| from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry | ||||
| from erpnext.controllers.accounts_controller import update_child_qty_rate | ||||
| from erpnext.controllers.status_updater import OverAllowanceError | ||||
| from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order | ||||
| 
 | ||||
| 
 | ||||
| class TestPurchaseOrder(unittest.TestCase): | ||||
| 	def test_make_purchase_receipt(self): | ||||
| @ -519,47 +521,62 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 	def test_backflush_based_on_stock_entry(self): | ||||
| 		item_code = "_Test Subcontracted FG Item 1" | ||||
| 		make_subcontracted_item(item_code) | ||||
| 		make_item('Sub Contracted Raw Material 1', { | ||||
| 			'is_stock_item': 1, | ||||
| 			'is_sub_contracted_item': 1 | ||||
| 		}) | ||||
| 
 | ||||
| 		update_backflush_based_on("Material Transferred for Subcontract") | ||||
| 		po = create_purchase_order(item_code=item_code, qty=1, | ||||
| 
 | ||||
| 		order_qty = 5 | ||||
| 		po = create_purchase_order(item_code=item_code, qty=order_qty, | ||||
| 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") | ||||
| 
 | ||||
| 		make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) | ||||
| 		make_stock_entry(target="_Test Warehouse - _TC", | ||||
| 			item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100) | ||||
| 		make_stock_entry(target="_Test Warehouse - _TC", | ||||
| 			item_code = "Test Extra Item 1", qty=100, basic_rate=100) | ||||
| 		make_stock_entry(target="_Test Warehouse - _TC", | ||||
| 			item_code = "Test Extra Item 2", qty=10, basic_rate=100) | ||||
| 		make_stock_entry(target="_Test Warehouse - _TC", | ||||
| 			item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100) | ||||
| 
 | ||||
| 		rm_item = [ | ||||
| 			{"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item", | ||||
| 				"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"}, | ||||
| 		rm_items = [ | ||||
| 			{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item", | ||||
| 				"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, | ||||
| 			{"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100", | ||||
| 				"qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}, | ||||
| 				"qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, | ||||
| 			{"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1", | ||||
| 				"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}] | ||||
| 				"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, | ||||
| 			{'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos', | ||||
| 				'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}] | ||||
| 
 | ||||
| 		rm_item_string = json.dumps(rm_item) | ||||
| 		rm_item_string = json.dumps(rm_items) | ||||
| 		se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) | ||||
| 		se.append('items', { | ||||
| 			'item_code': "Test Extra Item 2", | ||||
| 			"qty": 1, | ||||
| 			"rate": 100, | ||||
| 			"s_warehouse": "_Test Warehouse - _TC", | ||||
| 			"t_warehouse": "_Test Warehouse 1 - _TC" | ||||
| 		}) | ||||
| 		se.set_missing_values() | ||||
| 		se.submit() | ||||
| 
 | ||||
| 		pr = make_purchase_receipt(po.name) | ||||
| 
 | ||||
| 		received_qty = 2 | ||||
| 		# partial receipt | ||||
| 		pr.get('items')[0].qty = received_qty | ||||
| 		pr.save() | ||||
| 		pr.submit() | ||||
| 
 | ||||
| 		se_items = sorted([d.item_code for d in se.get('items')]) | ||||
| 		supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) | ||||
| 		transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name]) | ||||
| 		issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) | ||||
| 
 | ||||
| 		self.assertEquals(transferred_items, issued_items) | ||||
| 		self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000) | ||||
| 
 | ||||
| 
 | ||||
| 		transferred_rm_map = frappe._dict() | ||||
| 		for item in rm_items: | ||||
| 			transferred_rm_map[item.get('rm_item_code')] = item | ||||
| 
 | ||||
| 		for item in pr.get('supplied_items'): | ||||
| 			self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty) | ||||
| 
 | ||||
| 		self.assertEquals(se_items, supplied_items) | ||||
| 		update_backflush_based_on("BOM") | ||||
| 
 | ||||
| 	def test_advance_payment_entry_unlink_against_purchase_order(self): | ||||
| @ -605,6 +622,27 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 		po.save() | ||||
| 		self.assertEqual(po.schedule_date, add_days(nowdate(), 2)) | ||||
| 
 | ||||
| 	 | ||||
| 	def test_po_optional_blanket_order(self): | ||||
| 		""" | ||||
| 			Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1. | ||||
| 			Second Purchase Order should not add on to Blanket Orders Ordered Quantity. | ||||
| 		""" | ||||
| 
 | ||||
| 		bo = make_blanket_order(blanket_order_type = "Purchasing", quantity = 10, rate = 10) | ||||
| 
 | ||||
| 		po = create_purchase_order(item_code= "_Test Item", qty = 5, against_blanket_order = 1) | ||||
| 		po_doc = frappe.get_doc('Purchase Order', po.get('name')) | ||||
| 		# To test if the PO has a Blanket Order | ||||
| 		self.assertTrue(po_doc.items[0].blanket_order) | ||||
| 
 | ||||
| 		po = create_purchase_order(item_code= "_Test Item", qty = 5, against_blanket_order = 0) | ||||
| 		po_doc = frappe.get_doc('Purchase Order', po.get('name')) | ||||
| 		# To test if the PO does NOT have a Blanket Order | ||||
| 		self.assertEqual(po_doc.items[0].blanket_order, None) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| def make_pr_against_po(po, received_qty=0): | ||||
| 	pr = make_purchase_receipt(po) | ||||
| @ -678,7 +716,8 @@ def create_purchase_order(**args): | ||||
| 		"qty": args.qty or 10, | ||||
| 		"rate": args.rate or 500, | ||||
| 		"schedule_date": add_days(nowdate(), 1), | ||||
| 		"include_exploded_items": args.get('include_exploded_items', 1) | ||||
| 		"include_exploded_items": args.get('include_exploded_items', 1), | ||||
| 		"against_blanket_order": args.against_blanket_order | ||||
| 	}) | ||||
| 	if not args.do_not_save: | ||||
| 		po.insert() | ||||
|  | ||||
| @ -43,7 +43,6 @@ | ||||
|   "base_amount", | ||||
|   "pricing_rules", | ||||
|   "is_free_item", | ||||
|   "is_fixed_asset", | ||||
|   "section_break_29", | ||||
|   "net_rate", | ||||
|   "net_amount", | ||||
| @ -67,6 +66,7 @@ | ||||
|   "supplier_quotation", | ||||
|   "supplier_quotation_item", | ||||
|   "col_break5", | ||||
|   "against_blanket_order", | ||||
|   "blanket_order", | ||||
|   "blanket_order_rate", | ||||
|   "item_group", | ||||
| @ -511,6 +511,7 @@ | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.against_blanket_order", | ||||
|    "fieldname": "blanket_order", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Blanket Order", | ||||
| @ -518,6 +519,7 @@ | ||||
|    "options": "Blanket Order" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.against_blanket_order", | ||||
|    "fieldname": "blanket_order_rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Blanket Order Rate", | ||||
| @ -703,16 +705,14 @@ | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fetch_from": "item_code.is_fixed_asset", | ||||
|    "fieldname": "is_fixed_asset", | ||||
|    "fieldname": "against_blanket_order", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Is Fixed Asset", | ||||
|    "read_only": 1 | ||||
|    "label": "Against Blanket Order" | ||||
|   } | ||||
|  ], | ||||
|  "idx": 1, | ||||
|  "istable": 1, | ||||
|  "modified": "2019-11-07 17:19:12.090355", | ||||
|  "modified": "2019-11-19 14:10:52.865006", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Buying", | ||||
|  "name": "Purchase Order Item", | ||||
|  | ||||
| @ -56,3 +56,23 @@ class Supplier(TransactionBase): | ||||
| 	def after_rename(self, olddn, newdn, merge=False): | ||||
| 		if frappe.defaults.get_global_default('supp_master_name') == 'Supplier Name': | ||||
| 			frappe.db.set(self, "supplier_name", newdn) | ||||
| 
 | ||||
| 	def create_onboarding_docs(self, args): | ||||
| 		defaults = frappe.defaults.get_defaults() | ||||
| 		for i in range(1, args.get('max_count')): | ||||
| 			supplier = args.get('supplier_name_' + str(i)) | ||||
| 			if supplier: | ||||
| 				try: | ||||
| 					doc = frappe.get_doc({ | ||||
| 						'doctype': self.doctype, | ||||
| 						'supplier_name': supplier, | ||||
| 						'supplier_group': _('Local'), | ||||
| 						'company': defaults.get('company') | ||||
| 					}).insert() | ||||
| 
 | ||||
| 					if args.get('supplier_email_' + str(i)): | ||||
| 						from erpnext.selling.doctype.customer.customer import create_contact | ||||
| 						create_contact(supplier, 'Supplier', | ||||
| 							doc.name, args.get('supplier_email_' + str(i))) | ||||
| 				except frappe.NameError: | ||||
| 					pass | ||||
| @ -0,0 +1,49 @@ | ||||
| { | ||||
|  "add_more_button": 1, | ||||
|  "app": "ERPNext", | ||||
|  "creation": "2019-11-15 14:45:32.626641", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Setup Wizard Slide", | ||||
|  "domains": [], | ||||
|  "help_links": [ | ||||
|   { | ||||
|    "label": "Supplier", | ||||
|    "video_id": "zsrrVDk6VBs" | ||||
|   } | ||||
|  ], | ||||
|  "idx": 0, | ||||
|  "image_src": "/assets/erpnext/images/illustrations/supplier.png", | ||||
|  "max_count": 3, | ||||
|  "modified": "2019-11-26 18:26:25.498325", | ||||
|  "modified_by": "Administrator", | ||||
|  "name": "Add A Few Suppliers", | ||||
|  "owner": "Administrator", | ||||
|  "ref_doctype": "Supplier", | ||||
|  "slide_desc": "", | ||||
|  "slide_fields": [ | ||||
|   { | ||||
|    "align": "", | ||||
|    "fieldname": "supplier_name", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Supplier Name", | ||||
|    "placeholder": "", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "align": "", | ||||
|    "fieldtype": "Column Break", | ||||
|    "reqd": 0 | ||||
|   }, | ||||
|   { | ||||
|    "align": "", | ||||
|    "fieldname": "supplier_email", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Supplier Email", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "slide_order": 50, | ||||
|  "slide_title": "Add A Few Suppliers", | ||||
|  "slide_type": "Create", | ||||
|  "submit_method": "" | ||||
| } | ||||
| @ -197,6 +197,11 @@ def get_data(): | ||||
| 					"name": "Bank Reconciliation Statement", | ||||
| 					"is_query_report": True, | ||||
| 					"doctype": "Journal Entry" | ||||
| 				},{ | ||||
| 					"type": "page", | ||||
| 					"name": "bank-reconciliation", | ||||
| 					"label": _("Bank Reconciliation"), | ||||
| 					"icon": "fa fa-bar-chart" | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "report", | ||||
|  | ||||
| @ -241,6 +241,10 @@ def get_data(): | ||||
| 					"type": "doctype", | ||||
| 					"name": "Quality Inspection Template", | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Quick Stock Balance", | ||||
| 				}, | ||||
| 			] | ||||
| 		}, | ||||
| 		{ | ||||
|  | ||||
| @ -61,7 +61,6 @@ class AccountsController(TransactionBase): | ||||
| 						_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1) | ||||
| 
 | ||||
| 	def validate(self): | ||||
| 
 | ||||
| 		if not self.get('is_return'): | ||||
| 			self.validate_qty_is_not_zero() | ||||
| 
 | ||||
| @ -100,11 +99,23 @@ class AccountsController(TransactionBase): | ||||
| 
 | ||||
| 			if self.is_return: | ||||
| 				self.validate_qty() | ||||
| 			else: | ||||
| 				self.validate_deferred_start_and_end_date() | ||||
| 
 | ||||
| 		validate_regional(self) | ||||
| 		if self.doctype != 'Material Request': | ||||
| 			apply_pricing_rule_on_transaction(self) | ||||
| 
 | ||||
| 	def validate_deferred_start_and_end_date(self): | ||||
| 		for d in self.items: | ||||
| 			if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"): | ||||
| 				if not (d.service_start_date and d.service_end_date): | ||||
| 					frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx)) | ||||
| 				elif getdate(d.service_start_date) > getdate(d.service_end_date): | ||||
| 					frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx)) | ||||
| 				elif getdate(self.posting_date) > getdate(d.service_end_date): | ||||
| 					frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx)) | ||||
| 
 | ||||
| 	def validate_invoice_documents_schedule(self): | ||||
| 		self.validate_payment_schedule_dates() | ||||
| 		self.set_due_date() | ||||
| @ -415,9 +426,10 @@ class AccountsController(TransactionBase): | ||||
| 		return gl_dict | ||||
| 
 | ||||
| 	def validate_qty_is_not_zero(self): | ||||
| 		for item in self.items: | ||||
| 			if not item.qty: | ||||
| 				frappe.throw(_("Item quantity can not be zero")) | ||||
| 		if self.doctype != "Purchase Receipt": | ||||
| 			for item in self.items: | ||||
| 				if not item.qty: | ||||
| 					frappe.throw(_("Item quantity can not be zero")) | ||||
| 
 | ||||
| 	def validate_account_currency(self, account, account_currency=None): | ||||
| 		valid_currency = [self.company_currency] | ||||
|  | ||||
| @ -221,7 +221,7 @@ class BuyingController(StockController): | ||||
| 				"backflush_raw_materials_of_subcontract_based_on") | ||||
| 			if (self.doctype == 'Purchase Receipt' and | ||||
| 				backflush_raw_materials_based_on != 'BOM'): | ||||
| 				self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table) | ||||
| 				self.update_raw_materials_supplied_based_on_stock_entries() | ||||
| 			else: | ||||
| 				for item in self.get("items"): | ||||
| 					if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: | ||||
| @ -241,41 +241,95 @@ class BuyingController(StockController): | ||||
| 		if self.is_subcontracted == "No" and self.get("supplied_items"): | ||||
| 			self.set('supplied_items', []) | ||||
| 
 | ||||
| 	def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table): | ||||
| 		self.set(raw_material_table, []) | ||||
| 		purchase_orders = [d.purchase_order for d in self.items] | ||||
| 		if purchase_orders: | ||||
| 			items = get_subcontracted_raw_materials_from_se(purchase_orders) | ||||
| 			backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name) | ||||
| 	def update_raw_materials_supplied_based_on_stock_entries(self): | ||||
| 		self.set('supplied_items', []) | ||||
| 
 | ||||
| 			for d in items: | ||||
| 				qty = d.qty - backflushed_raw_materials.get(d.item_code, 0) | ||||
| 				rm = self.append(raw_material_table, {}) | ||||
| 				rm.rm_item_code = d.item_code | ||||
| 				rm.item_name = d.item_name | ||||
| 				rm.main_item_code = d.main_item_code | ||||
| 				rm.description = d.description | ||||
| 				rm.stock_uom = d.stock_uom | ||||
| 				rm.required_qty = qty | ||||
| 				rm.consumed_qty = qty | ||||
| 				rm.serial_no = d.serial_no | ||||
| 				rm.batch_no = d.batch_no | ||||
| 		purchase_orders = set([d.purchase_order for d in self.items]) | ||||
| 
 | ||||
| 				# get raw materials rate | ||||
| 				from erpnext.stock.utils import get_incoming_rate | ||||
| 				rm.rate = get_incoming_rate({ | ||||
| 					"item_code": d.item_code, | ||||
| 					"warehouse": self.supplier_warehouse, | ||||
| 					"posting_date": self.posting_date, | ||||
| 					"posting_time": self.posting_time, | ||||
| 					"qty": -1 * qty, | ||||
| 					"serial_no": rm.serial_no | ||||
| 				}) | ||||
| 				if not rm.rate: | ||||
| 					rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse, | ||||
| 						self.doctype, self.name, currency=self.company_currency, company = self.company) | ||||
| 		# qty of raw materials backflushed (for each item per purchase order) | ||||
| 		backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders) | ||||
| 
 | ||||
| 				rm.amount = qty * flt(rm.rate) | ||||
| 		# qty of "finished good" item yet to be received | ||||
| 		qty_to_be_received_map = get_qty_to_be_received(purchase_orders) | ||||
| 
 | ||||
| 		for item in self.get('items'): | ||||
| 			# reset raw_material cost | ||||
| 			item.rm_supp_cost = 0 | ||||
| 
 | ||||
| 			# qty of raw materials transferred to the supplier | ||||
| 			transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code) | ||||
| 
 | ||||
| 			non_stock_items = get_non_stock_items(item.purchase_order, item.item_code) | ||||
| 
 | ||||
| 			item_key = '{}{}'.format(item.item_code, item.purchase_order) | ||||
| 
 | ||||
| 			fg_yet_to_be_received = qty_to_be_received_map.get(item_key) | ||||
| 
 | ||||
| 			raw_material_data = backflushed_raw_materials_map.get(item_key, {}) | ||||
| 
 | ||||
| 			consumed_qty = raw_material_data.get('qty', 0) | ||||
| 			consumed_serial_nos = raw_material_data.get('serial_nos', '') | ||||
| 			consumed_batch_nos = raw_material_data.get('batch_nos', '') | ||||
| 
 | ||||
| 			transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code) | ||||
| 			backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) | ||||
| 
 | ||||
| 			for raw_material in transferred_raw_materials + non_stock_items: | ||||
| 				transferred_qty = raw_material.qty | ||||
| 
 | ||||
| 				rm_qty_to_be_consumed = transferred_qty - consumed_qty | ||||
| 
 | ||||
| 				# backflush all remaining transferred qty in the last Purchase Receipt | ||||
| 				if fg_yet_to_be_received == item.qty: | ||||
| 					qty = rm_qty_to_be_consumed | ||||
| 				else: | ||||
| 					qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty | ||||
| 
 | ||||
| 					if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'): | ||||
| 						qty = frappe.utils.ceil(qty) | ||||
| 
 | ||||
| 				if qty > rm_qty_to_be_consumed: | ||||
| 					qty = rm_qty_to_be_consumed | ||||
| 
 | ||||
| 				if not qty: continue | ||||
| 
 | ||||
| 				if raw_material.serial_nos: | ||||
| 					set_serial_nos(raw_material, consumed_serial_nos, qty) | ||||
| 
 | ||||
| 				if raw_material.batch_nos: | ||||
| 					batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code, | ||||
| 						qty, transferred_batch_qty_map, backflushed_batch_qty_map) | ||||
| 					for batch_data in batches_qty: | ||||
| 						qty = batch_data['qty'] | ||||
| 						raw_material.batch_no = batch_data['batch'] | ||||
| 						self.append_raw_material_to_be_backflushed(item, raw_material, qty) | ||||
| 				else: | ||||
| 					self.append_raw_material_to_be_backflushed(item, raw_material, qty) | ||||
| 
 | ||||
| 	def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty): | ||||
| 		rm = self.append('supplied_items', {}) | ||||
| 		rm.update(raw_material_data) | ||||
| 
 | ||||
| 		rm.required_qty = qty | ||||
| 		rm.consumed_qty = qty | ||||
| 
 | ||||
| 		if not raw_material_data.get('non_stock_item'): | ||||
| 			from erpnext.stock.utils import get_incoming_rate | ||||
| 			rm.rate = get_incoming_rate({ | ||||
| 				"item_code": raw_material_data.rm_item_code, | ||||
| 				"warehouse": self.supplier_warehouse, | ||||
| 				"posting_date": self.posting_date, | ||||
| 				"posting_time": self.posting_time, | ||||
| 				"qty": -1 * qty, | ||||
| 				"serial_no": rm.serial_no | ||||
| 			}) | ||||
| 
 | ||||
| 			if not rm.rate: | ||||
| 				rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse, | ||||
| 					self.doctype, self.name, currency=self.company_currency, company=self.company) | ||||
| 
 | ||||
| 		rm.amount = qty * flt(rm.rate) | ||||
| 		fg_item_doc.rm_supp_cost += rm.amount | ||||
| 
 | ||||
| 	def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table): | ||||
| 		exploded_item = 1 | ||||
| @ -387,9 +441,11 @@ class BuyingController(StockController): | ||||
| 			item_codes = list(set(item.item_code for item in | ||||
| 				self.get("items"))) | ||||
| 			if item_codes: | ||||
| 				self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name | ||||
| 					from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \ | ||||
| 					(", ".join((["%s"]*len(item_codes))),), item_codes)] | ||||
| 				items = frappe.get_all('Item', filters={ | ||||
| 					'name': ['in', item_codes], | ||||
| 					'is_sub_contracted_item': 1 | ||||
| 				}) | ||||
| 				self._sub_contracted_items = [item.name for item in items] | ||||
| 
 | ||||
| 		return self._sub_contracted_items | ||||
| 
 | ||||
| @ -722,28 +778,72 @@ def get_items_from_bom(item_code, bom, exploded_item=1): | ||||
| 
 | ||||
| 	return bom_items | ||||
| 
 | ||||
| def get_subcontracted_raw_materials_from_se(purchase_orders): | ||||
| 	return frappe.db.sql(""" | ||||
| 		select | ||||
| 			sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description, | ||||
| 			sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no | ||||
| 		from `tabStock Entry` se,`tabStock Entry Detail` sed | ||||
| 		where | ||||
| 			se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor' | ||||
| 			and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != '' | ||||
| 		group by sed.item_code, sed.t_warehouse | ||||
| 	""" % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1) | ||||
| def get_subcontracted_raw_materials_from_se(purchase_order, fg_item): | ||||
| 	common_query = """ | ||||
| 		SELECT | ||||
| 			sed.item_code AS rm_item_code, | ||||
| 			SUM(sed.qty) AS qty, | ||||
| 			sed.description, | ||||
| 			sed.stock_uom, | ||||
| 			sed.subcontracted_item AS main_item_code, | ||||
| 			{serial_no_concat_syntax} AS serial_nos, | ||||
| 			{batch_no_concat_syntax} AS batch_nos | ||||
| 		FROM `tabStock Entry` se,`tabStock Entry Detail` sed | ||||
| 		WHERE | ||||
| 			se.name = sed.parent | ||||
| 			AND se.docstatus=1 | ||||
| 			AND se.purpose='Send to Subcontractor' | ||||
| 			AND se.purchase_order = %s | ||||
| 			AND IFNULL(sed.t_warehouse, '') != '' | ||||
| 			AND sed.subcontracted_item = %s | ||||
| 		GROUP BY sed.item_code, sed.subcontracted_item | ||||
| 	""" | ||||
| 	raw_materials = frappe.db.multisql({ | ||||
| 		'mariadb': common_query.format( | ||||
| 			serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)", | ||||
| 			batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)" | ||||
| 		), | ||||
| 		'postgres': common_query.format( | ||||
| 			serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')", | ||||
| 			batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')" | ||||
| 		) | ||||
| 	}, (purchase_order, fg_item), as_dict=1) | ||||
| 
 | ||||
| def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt): | ||||
| 	return frappe._dict(frappe.db.sql(""" | ||||
| 		select | ||||
| 			prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty | ||||
| 		from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi | ||||
| 		where | ||||
| 			pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s) | ||||
| 			and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1 | ||||
| 		group by prsi.rm_item_code | ||||
| 	""" % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders))) | ||||
| 	return raw_materials | ||||
| 
 | ||||
| def get_backflushed_subcontracted_raw_materials(purchase_orders): | ||||
| 	common_query = """ | ||||
| 		SELECT | ||||
| 			CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key, | ||||
| 			SUM(prsi.consumed_qty) AS qty, | ||||
| 			{serial_no_concat_syntax} AS serial_nos, | ||||
| 			{batch_no_concat_syntax} AS batch_nos | ||||
| 		FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi | ||||
| 		WHERE | ||||
| 			pr.name = pri.parent | ||||
| 			AND pr.name = prsi.parent | ||||
| 			AND pri.purchase_order IN %s | ||||
| 			AND pri.item_code = prsi.main_item_code | ||||
| 			AND pr.docstatus = 1 | ||||
| 		GROUP BY prsi.rm_item_code, pri.purchase_order | ||||
| 	""" | ||||
| 
 | ||||
| 	backflushed_raw_materials = frappe.db.multisql({ | ||||
| 		'mariadb': common_query.format( | ||||
| 			serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)", | ||||
| 			batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)" | ||||
| 		), | ||||
| 		'postgres': common_query.format( | ||||
| 			serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')", | ||||
| 			batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')" | ||||
| 		) | ||||
| 	}, (purchase_orders, ), as_dict=1) | ||||
| 
 | ||||
| 	backflushed_raw_materials_map = frappe._dict() | ||||
| 	for item in backflushed_raw_materials: | ||||
| 		backflushed_raw_materials_map.setdefault(item.item_key, item) | ||||
| 
 | ||||
| 	return backflushed_raw_materials_map | ||||
| 
 | ||||
| def get_asset_item_details(asset_items): | ||||
| 	asset_items_data = {} | ||||
| @ -776,3 +876,125 @@ def validate_item_type(doc, fieldname, message): | ||||
| 			error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message)) | ||||
| 
 | ||||
| 		frappe.throw(error_message) | ||||
| 
 | ||||
| def get_qty_to_be_received(purchase_orders): | ||||
| 	return frappe._dict(frappe.db.sql(""" | ||||
| 		SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key, | ||||
| 		SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received | ||||
| 		FROM `tabPurchase Order Item` poi | ||||
| 		WHERE | ||||
| 			poi.`parent` in %s | ||||
| 		GROUP BY poi.`item_code`, poi.`parent` | ||||
| 		HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`) | ||||
| 	""", (purchase_orders))) | ||||
| 
 | ||||
| def get_non_stock_items(purchase_order, fg_item_code): | ||||
| 	return frappe.db.sql(""" | ||||
| 		SELECT | ||||
| 			pois.main_item_code, | ||||
| 			pois.rm_item_code, | ||||
| 			item.description, | ||||
| 			pois.required_qty AS qty, | ||||
| 			pois.rate, | ||||
| 			1 as non_stock_item, | ||||
| 			pois.stock_uom | ||||
| 		FROM `tabPurchase Order Item Supplied` pois, `tabItem` item | ||||
| 		WHERE | ||||
| 			pois.`rm_item_code` = item.`name` | ||||
| 			AND item.is_stock_item = 0 | ||||
| 			AND pois.`parent` = %s | ||||
| 			AND pois.`main_item_code` = %s | ||||
| 	""", (purchase_order, fg_item_code), as_dict=1) | ||||
| 
 | ||||
| 
 | ||||
| def set_serial_nos(raw_material, consumed_serial_nos, qty): | ||||
| 	serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \ | ||||
| 		set(get_serial_nos(consumed_serial_nos)) | ||||
| 	if serial_nos and qty <= len(serial_nos): | ||||
| 		raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)]) | ||||
| 
 | ||||
| def get_transferred_batch_qty_map(purchase_order, fg_item): | ||||
| 	# returns | ||||
| 	# { | ||||
| 	# 	(item_code, fg_code): { | ||||
| 	# 		batch1: 10, # qty | ||||
| 	# 		batch2: 16 | ||||
| 	# 	}, | ||||
| 	# } | ||||
| 	transferred_batch_qty_map = {} | ||||
| 	transferred_batches = frappe.db.sql(""" | ||||
| 		SELECT | ||||
| 			sed.batch_no, | ||||
| 			SUM(sed.qty) AS qty, | ||||
| 			sed.item_code | ||||
| 		FROM `tabStock Entry` se,`tabStock Entry Detail` sed | ||||
| 		WHERE | ||||
| 			se.name = sed.parent | ||||
| 			AND se.docstatus=1 | ||||
| 			AND se.purpose='Send to Subcontractor' | ||||
| 			AND se.purchase_order = %s | ||||
| 			AND sed.subcontracted_item = %s | ||||
| 			AND sed.batch_no IS NOT NULL | ||||
| 		GROUP BY | ||||
| 			sed.batch_no, | ||||
| 			sed.item_code | ||||
| 	""", (purchase_order, fg_item), as_dict=1) | ||||
| 
 | ||||
| 	for batch_data in transferred_batches: | ||||
| 		transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) | ||||
| 		transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty | ||||
| 
 | ||||
| 	return transferred_batch_qty_map | ||||
| 
 | ||||
| def get_backflushed_batch_qty_map(purchase_order, fg_item): | ||||
| 	# returns | ||||
| 	# { | ||||
| 	# 	(item_code, fg_code): { | ||||
| 	# 		batch1: 10, # qty | ||||
| 	# 		batch2: 16 | ||||
| 	# 	}, | ||||
| 	# } | ||||
| 	backflushed_batch_qty_map = {} | ||||
| 	backflushed_batches = frappe.db.sql(""" | ||||
| 		SELECT | ||||
| 			pris.batch_no, | ||||
| 			SUM(pris.consumed_qty) AS qty, | ||||
| 			pris.rm_item_code AS item_code | ||||
| 		FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris | ||||
| 		WHERE | ||||
| 			pr.name = pri.parent | ||||
| 			AND pri.parent = pris.parent | ||||
| 			AND pri.purchase_order = %s | ||||
| 			AND pri.item_code = pris.main_item_code | ||||
| 			AND pr.docstatus = 1 | ||||
| 			AND pris.main_item_code = %s | ||||
| 			AND pris.batch_no IS NOT NULL | ||||
| 		GROUP BY | ||||
| 			pris.rm_item_code, pris.batch_no | ||||
| 	""", (purchase_order, fg_item), as_dict=1) | ||||
| 
 | ||||
| 	for batch_data in backflushed_batches: | ||||
| 		backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) | ||||
| 		backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty | ||||
| 
 | ||||
| 	return backflushed_batch_qty_map | ||||
| 
 | ||||
| def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map): | ||||
| 	# Returns available batches to be backflushed based on requirements | ||||
| 	transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {}) | ||||
| 	backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {}) | ||||
| 
 | ||||
| 	available_batches = [] | ||||
| 
 | ||||
| 	for (batch, transferred_qty) in transferred_batches.items(): | ||||
| 		backflushed_qty = backflushed_batches.get(batch, 0) | ||||
| 		available_qty = transferred_qty - backflushed_qty | ||||
| 
 | ||||
| 		if available_qty >= required_qty: | ||||
| 			available_batches.append({'batch': batch, 'qty': required_qty}) | ||||
| 			break | ||||
| 		else: | ||||
| 			available_batches.append({'batch': batch, 'qty': available_qty}) | ||||
| 			required_qty -= available_qty | ||||
| 
 | ||||
| 	return available_batches | ||||
| @ -41,7 +41,8 @@ class EmailCampaign(Document): | ||||
| 		email_campaign_exists = frappe.db.exists("Email Campaign", { | ||||
| 			"campaign_name": self.campaign_name, | ||||
| 			"recipient": self.recipient, | ||||
| 			"status": ("in", ["In Progress", "Scheduled"]) | ||||
| 			"status": ("in", ["In Progress", "Scheduled"]), | ||||
| 			"name": ("!=", self.name) | ||||
| 		}) | ||||
| 		if email_campaign_exists: | ||||
| 			frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient)) | ||||
| @ -78,7 +79,7 @@ def send_mail(entry, email_campaign): | ||||
| 	comm = make( | ||||
| 		doctype = "Email Campaign", | ||||
| 		name = email_campaign.name, | ||||
| 		subject = email_template.get("subject"), | ||||
| 		subject = frappe.render_template(email_template.get("subject"), context), | ||||
| 		content = frappe.render_template(email_template.get("response"), context), | ||||
| 		sender = sender, | ||||
| 		recipients = recipient, | ||||
|  | ||||
| @ -130,10 +130,10 @@ class Opportunity(TransactionBase): | ||||
| 
 | ||||
| 	def has_lost_quotation(self): | ||||
| 		lost_quotation = frappe.db.sql(""" | ||||
| 			select q.name | ||||
| 			from `tabQuotation` q, `tabQuotation Item` qi | ||||
| 			where q.name = qi.parent and q.docstatus=1 | ||||
| 				and qi.prevdoc_docname =%s and q.status = 'Lost' | ||||
| 			select name | ||||
| 			from `tabQuotation` | ||||
| 			where docstatus=1 | ||||
| 				and opportunity =%s and status = 'Lost' | ||||
| 			""", self.name) | ||||
| 		if lost_quotation: | ||||
| 			if self.has_active_quotation(): | ||||
|  | ||||
| @ -71,7 +71,7 @@ class ProgramEnrollment(Document): | ||||
| 	def create_course_enrollments(self): | ||||
| 		student = frappe.get_doc("Student", self.student) | ||||
| 		program = frappe.get_doc("Program", self.program) | ||||
| 		course_list = [course.course for course in program.get_all_children()] | ||||
| 		course_list = [course.course for course in program.courses] | ||||
| 		for course_name in course_list: | ||||
| 			student.enroll_in_course(course_name=course_name, program_enrollment=self.name) | ||||
| 
 | ||||
|  | ||||
| @ -40,7 +40,7 @@ class Student(Document): | ||||
| 			frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant)) | ||||
| 
 | ||||
| 	def after_insert(self): | ||||
| 		if not frappe.get_single('Education Settings').user_creation_skip: | ||||
| 		if not frappe.get_single('Education Settings').get('user_creation_skip'): | ||||
| 			self.create_student_user() | ||||
| 
 | ||||
| 	def create_student_user(self): | ||||
|  | ||||
| @ -63,10 +63,11 @@ def updating_rate(self): | ||||
| 	 item_code=%s""",(self.template, self.rate, self.item)) | ||||
| 
 | ||||
| def create_item_from_template(doc): | ||||
| 	disabled = 1 | ||||
| 	 | ||||
| 	if(doc.is_billable == 1): | ||||
| 		disabled = 0 | ||||
| 	else: | ||||
| 		disabled = 1 | ||||
| 
 | ||||
| 	#insert item | ||||
| 	item =  frappe.get_doc({ | ||||
| 	"doctype": "Item", | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils import today, now_datetime | ||||
| from frappe.utils import today, now_datetime, getdate | ||||
| from frappe.model.document import Document | ||||
| from frappe.desk.reportview import get_match_cond | ||||
| 
 | ||||
| @ -15,11 +15,20 @@ class InpatientRecord(Document): | ||||
| 		frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name) | ||||
| 
 | ||||
| 	def validate(self): | ||||
| 		self.validate_dates() | ||||
| 		self.validate_already_scheduled_or_admitted() | ||||
| 		if self.status == "Discharged": | ||||
| 			frappe.db.set_value("Patient", self.patient, "inpatient_status", None) | ||||
| 			frappe.db.set_value("Patient", self.patient, "inpatient_record", None) | ||||
| 
 | ||||
| 	def validate_dates(self): | ||||
| 		if (getdate(self.scheduled_date) < getdate(today())) or \ | ||||
| 			(getdate(self.admitted_datetime) < getdate(today())): | ||||
| 				frappe.throw(_("Scheduled and Admitted dates can not be less than today")) | ||||
| 		if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \ | ||||
| 			(getdate(self.discharge_date) < getdate(self.scheduled_date)): | ||||
| 			frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date")) | ||||
| 	 | ||||
| 	def validate_already_scheduled_or_admitted(self): | ||||
| 		query = """ | ||||
| 			select name, status | ||||
|  | ||||
| @ -40,8 +40,6 @@ after_install = "erpnext.setup.install.after_install" | ||||
| boot_session = "erpnext.startup.boot.boot_session" | ||||
| notification_config = "erpnext.startup.notifications.get_notification_config" | ||||
| get_help_messages = "erpnext.utilities.activation.get_help_messages" | ||||
| get_user_progress_slides = "erpnext.utilities.user_progress.get_user_progress_slides" | ||||
| update_and_get_user_progress = "erpnext.utilities.user_progress_utils.update_default_domain_actions_and_get_state" | ||||
| leaderboards = "erpnext.startup.leaderboard.get_leaderboards" | ||||
| 
 | ||||
| 
 | ||||
| @ -302,7 +300,7 @@ scheduler_events = { | ||||
| 		"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status", | ||||
| 		"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts", | ||||
| 		"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status", | ||||
| 		"erpnext.selling.doctype.quotation.set_expired_status" | ||||
| 		"erpnext.selling.doctype.quotation.quotation.set_expired_status" | ||||
| 	], | ||||
| 	"daily_long": [ | ||||
| 		"erpnext.setup.doctype.email_digest.email_digest.send", | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| 
 | ||||
| from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime | ||||
| from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr | ||||
| from frappe.model.naming import set_name_by_naming_series | ||||
| from frappe import throw, _, scrub | ||||
| from frappe.permissions import add_user_permission, remove_user_permission, \ | ||||
| @ -152,8 +152,8 @@ class Employee(NestedSet): | ||||
| 		elif self.date_of_retirement and self.date_of_joining and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining)): | ||||
| 			throw(_("Date Of Retirement must be greater than Date of Joining")) | ||||
| 
 | ||||
| 		elif self.relieving_date and self.date_of_joining and (getdate(self.relieving_date) <= getdate(self.date_of_joining)): | ||||
| 			throw(_("Relieving Date must be greater than Date of Joining")) | ||||
| 		elif self.relieving_date and self.date_of_joining and (getdate(self.relieving_date) < getdate(self.date_of_joining)): | ||||
| 			throw(_("Relieving Date must be greater than or equal to Date of Joining")) | ||||
| 
 | ||||
| 		elif self.contract_end_date and self.date_of_joining and (getdate(self.contract_end_date) <= getdate(self.date_of_joining)): | ||||
| 			throw(_("Contract End Date must be greater than Date of Joining")) | ||||
| @ -218,8 +218,8 @@ class Employee(NestedSet): | ||||
| 
 | ||||
| 	def reset_employee_emails_cache(self): | ||||
| 		prev_doc = self.get_doc_before_save() or {} | ||||
| 		cell_number = self.get('cell_number') | ||||
| 		prev_number = prev_doc.get('cell_number') | ||||
| 		cell_number = cstr(self.get('cell_number')) | ||||
| 		prev_number = cstr(prev_doc.get('cell_number')) | ||||
| 		if (cell_number != prev_number or | ||||
| 			self.get('user_id') != prev_doc.get('user_id')): | ||||
| 			frappe.cache().hdel('employees_with_number', cell_number) | ||||
|  | ||||
| @ -31,7 +31,11 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 			} | ||||
| 			if ((frm.doc.employees || []).length) { | ||||
| 				frm.page.set_primary_action(__('Create Salary Slips'), () => { | ||||
| 					frm.save('Submit'); | ||||
| 					frm.save('Submit').then(()=>{ | ||||
| 						frm.page.clear_primary_action(); | ||||
| 						frm.refresh(); | ||||
| 						frm.events.refresh(frm); | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -1,231 +1,82 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "beta": 0,  | ||||
|  "creation": "2016-12-20 15:32:25.078334",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "creation": "2016-12-20 15:32:25.078334", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "payment_date", | ||||
|   "principal_amount", | ||||
|   "interest_amount", | ||||
|   "total_payment", | ||||
|   "balance_loan_amount", | ||||
|   "paid" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "fieldname": "payment_date",  | ||||
|    "fieldtype": "Date",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Payment Date",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "allow_on_submit": 1, | ||||
|    "columns": 2, | ||||
|    "fieldname": "payment_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Payment Date" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "fieldname": "principal_amount",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Principal Amount",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "fieldname": "principal_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Principal Amount", | ||||
|    "no_copy": 1, | ||||
|    "options": "Company:company:default_currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "fieldname": "interest_amount",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Interest Amount",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "fieldname": "interest_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Interest Amount", | ||||
|    "no_copy": 1, | ||||
|    "options": "Company:company:default_currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "fieldname": "total_payment",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Total Payment",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "fieldname": "total_payment", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Total Payment", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "fieldname": "balance_loan_amount",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Balance Loan Amount",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "fieldname": "balance_loan_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Balance Loan Amount", | ||||
|    "no_copy": 1, | ||||
|    "options": "Company:company:default_currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "paid",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Paid",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|    "default": "0", | ||||
|    "fieldname": "paid", | ||||
|    "fieldtype": "Check", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Paid", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 1,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2018-03-30 17:37:31.834792",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "HR",  | ||||
|  "name": "Repayment Schedule",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "modified": "2019-10-29 11:45:10.694557", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "HR", | ||||
|  "name": "Repayment Schedule", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -44,6 +44,8 @@ def make_sales_order(source_name): | ||||
| 			target.item_name = item.get("item_name") | ||||
| 			target.description = item.get("description") | ||||
| 			target.uom = item.get("stock_uom") | ||||
| 			target.against_blanket_order = 1 | ||||
| 			target.blanket_order = source_name | ||||
| 
 | ||||
| 	target_doc = get_mapped_doc("Blanket Order", source_name, { | ||||
| 		"Blanket Order": { | ||||
| @ -71,6 +73,8 @@ def make_purchase_order(source_name): | ||||
| 			target.description = item.get("description") | ||||
| 			target.uom = item.get("stock_uom") | ||||
| 			target.warehouse = item.get("default_warehouse") | ||||
| 			target.against_blanket_order = 1 | ||||
| 			target.blanket_order = source_name | ||||
| 
 | ||||
| 	target_doc = get_mapped_doc("Blanket Order", source_name, { | ||||
| 		"Blanket Order": { | ||||
|  | ||||
| @ -606,6 +606,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite | ||||
| 				item.image, | ||||
| 				bom.project, | ||||
| 				item.stock_uom, | ||||
| 				item.item_group, | ||||
| 				item.allow_alternative_item, | ||||
| 				item_default.default_warehouse, | ||||
| 				item_default.expense_account as expense_account, | ||||
|  | ||||
| @ -9,6 +9,7 @@ from frappe import _ | ||||
| from six import string_types | ||||
| from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order | ||||
| from frappe.model.document import Document | ||||
| import click | ||||
| 
 | ||||
| class BOMUpdateTool(Document): | ||||
| 	def replace_bom(self): | ||||
| @ -17,7 +18,8 @@ class BOMUpdateTool(Document): | ||||
| 		frappe.cache().delete_key('bom_children') | ||||
| 		bom_list = self.get_parent_boms(self.new_bom) | ||||
| 		updated_bom = [] | ||||
| 
 | ||||
| 		with click.progressbar(bom_list) as bom_list: | ||||
| 			pass | ||||
| 		for bom in bom_list: | ||||
| 			try: | ||||
| 				bom_obj = frappe.get_cached_doc('BOM', bom) | ||||
|  | ||||
| @ -4,10 +4,16 @@ | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| import datetime | ||||
| from frappe import _ | ||||
| from frappe.utils import flt, time_diff_in_hours, get_datetime | ||||
| from frappe.model.mapper import get_mapped_doc | ||||
| from frappe.model.document import Document | ||||
| from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,  | ||||
| 	get_time, add_to_date, time_diff, add_days, get_datetime_str) | ||||
| 
 | ||||
| from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations | ||||
| 
 | ||||
| class OverlapError(frappe.ValidationError): pass | ||||
| 
 | ||||
| class JobCard(Document): | ||||
| 	def validate(self): | ||||
| @ -26,7 +32,7 @@ class JobCard(Document): | ||||
| 				data = self.get_overlap_for(d) | ||||
| 				if data: | ||||
| 					frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}") | ||||
| 						.format(d.idx, self.name, data.name)) | ||||
| 						.format(d.idx, self.name, data.name), OverlapError) | ||||
| 
 | ||||
| 				if d.from_time and d.to_time: | ||||
| 					d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60 | ||||
| @ -35,27 +41,120 @@ class JobCard(Document): | ||||
| 				if d.completed_qty: | ||||
| 					self.total_completed_qty += d.completed_qty | ||||
| 
 | ||||
| 	def get_overlap_for(self, args): | ||||
| 		existing = frappe.db.sql("""select jc.name as name from | ||||
| 	def get_overlap_for(self, args, check_next_available_slot=False): | ||||
| 		production_capacity = 1 | ||||
| 	 | ||||
| 		if self.workstation: | ||||
| 			production_capacity = frappe.get_cached_value("Workstation", | ||||
| 				self.workstation, 'production_capacity') or 1 | ||||
| 			validate_overlap_for = " and jc.workstation = %(workstation)s " | ||||
| 
 | ||||
| 		if self.employee: | ||||
| 			# override capacity for employee | ||||
| 			production_capacity = 1 | ||||
| 			validate_overlap_for = " and jc.employee = %(employee)s " | ||||
| 
 | ||||
| 		extra_cond = '' | ||||
| 		if check_next_available_slot: | ||||
| 			extra_cond = " or (%(from_time)s <= jctl.from_time and %(to_time)s <= jctl.to_time)" | ||||
| 
 | ||||
| 		existing = frappe.db.sql("""select jc.name as name, jctl.to_time from | ||||
| 			`tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and | ||||
| 			( | ||||
| 				(%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or | ||||
| 				(%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or | ||||
| 				(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time)) | ||||
| 			and jctl.name!=%(name)s | ||||
| 			and jc.name!=%(parent)s | ||||
| 			and jc.docstatus < 2 | ||||
| 			and jc.employee = %(employee)s """, | ||||
| 				(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0} | ||||
| 			) | ||||
| 			and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1} | ||||
| 			order by jctl.to_time desc limit 1""".format(extra_cond, validate_overlap_for), | ||||
| 			{ | ||||
| 				"from_time": args.from_time, | ||||
| 				"to_time": args.to_time, | ||||
| 				"name": args.name or "No Name", | ||||
| 				"parent": args.parent or "No Name", | ||||
| 				"employee": self.employee | ||||
| 				"employee": self.employee, | ||||
| 				"workstation": self.workstation | ||||
| 			}, as_dict=True) | ||||
| 
 | ||||
| 		if existing and production_capacity > len(existing): | ||||
| 			return | ||||
| 
 | ||||
| 		return existing[0] if existing else None | ||||
| 
 | ||||
| 	def schedule_time_logs(self, row): | ||||
| 		row.remaining_time_in_mins = row.time_in_mins | ||||
| 		while row.remaining_time_in_mins > 0: | ||||
| 			args = frappe._dict({ | ||||
| 				"from_time": row.planned_start_time, | ||||
| 				"to_time": row.planned_end_time | ||||
| 			}) | ||||
| 
 | ||||
| 			self.validate_overlap_for_workstation(args, row) | ||||
| 			self.check_workstation_time(row) | ||||
| 
 | ||||
| 	def validate_overlap_for_workstation(self, args, row): | ||||
| 		# get the last record based on the to time from the job card | ||||
| 		data = self.get_overlap_for(args, check_next_available_slot=True) | ||||
| 		if data: | ||||
| 			row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations()) | ||||
| 
 | ||||
| 	def check_workstation_time(self, row): | ||||
| 		workstation_doc = frappe.get_cached_doc("Workstation", self.workstation) | ||||
| 		if (not workstation_doc.working_hours or | ||||
| 			cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))): | ||||
| 			row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time, | ||||
| 				row.planned_start_time) | ||||
| 
 | ||||
| 			self.update_time_logs(row) | ||||
| 			return | ||||
| 
 | ||||
| 		start_date = getdate(row.planned_start_time) | ||||
| 		start_time = get_time(row.planned_start_time) | ||||
| 
 | ||||
| 		new_start_date = workstation_doc.validate_workstation_holiday(start_date) | ||||
| 
 | ||||
| 		if new_start_date != start_date: | ||||
| 			row.planned_start_time = datetime.datetime.combine(new_start_date, start_time) | ||||
| 			start_date = new_start_date | ||||
| 
 | ||||
| 		total_idx = len(workstation_doc.working_hours) | ||||
| 
 | ||||
| 		for i, time_slot in enumerate(workstation_doc.working_hours): | ||||
| 			workstation_start_time = datetime.datetime.combine(start_date, get_time(time_slot.start_time)) | ||||
| 			workstation_end_time = datetime.datetime.combine(start_date, get_time(time_slot.end_time)) | ||||
| 
 | ||||
| 			if (get_datetime(row.planned_start_time) >= workstation_start_time and | ||||
| 				get_datetime(row.planned_start_time) <= workstation_end_time): | ||||
| 				time_in_mins = time_diff_in_minutes(workstation_end_time, row.planned_start_time) | ||||
| 
 | ||||
| 				# If remaining time fit in workstation time logs else split hours as per workstation time | ||||
| 				if time_in_mins > row.remaining_time_in_mins: | ||||
| 					row.planned_end_time = add_to_date(row.planned_start_time, | ||||
| 						minutes=row.remaining_time_in_mins) | ||||
| 					row.remaining_time_in_mins = 0 | ||||
| 				else: | ||||
| 					row.planned_end_time = add_to_date(row.planned_start_time, minutes=time_in_mins) | ||||
| 					row.remaining_time_in_mins -= time_in_mins | ||||
| 
 | ||||
| 				self.update_time_logs(row) | ||||
| 
 | ||||
| 				if total_idx != (i+1) and row.remaining_time_in_mins > 0: | ||||
| 					row.planned_start_time = datetime.datetime.combine(start_date, | ||||
| 						get_time(workstation_doc.working_hours[i+1].start_time)) | ||||
| 
 | ||||
| 		if row.remaining_time_in_mins > 0: | ||||
| 			start_date = add_days(start_date, 1) | ||||
| 			row.planned_start_time = datetime.datetime.combine(start_date, | ||||
| 				get_time(workstation_doc.working_hours[0].start_time)) | ||||
| 
 | ||||
| 	def update_time_logs(self, row): | ||||
| 		self.append("time_logs", { | ||||
| 			"from_time": row.planned_start_time, | ||||
| 			"to_time": row.planned_end_time, | ||||
| 			"completed_qty": 0, | ||||
| 			"time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time), | ||||
| 		}) | ||||
| 
 | ||||
| 	def get_required_items(self): | ||||
| 		if not self.get('work_order'): | ||||
| 			return | ||||
| @ -251,3 +350,6 @@ def make_stock_entry(source_name, target_doc=None): | ||||
| 	}, target_doc, set_missing_values) | ||||
| 
 | ||||
| 	return doclist | ||||
| 
 | ||||
| def time_diff_in_minutes(string_ed_date, string_st_date): | ||||
| 	return time_diff(string_ed_date, string_st_date).total_seconds() / 60 | ||||
| @ -1,585 +1,178 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "beta": 0,  | ||||
|  "creation": "2014-11-27 14:12:07.542534",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "Document",  | ||||
|  "editable_grid": 0,  | ||||
|  "engine": "InnoDB",  | ||||
|  "creation": "2014-11-27 14:12:07.542534", | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "Document", | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "raw_materials_consumption_section", | ||||
|   "material_consumption", | ||||
|   "column_break_3", | ||||
|   "backflush_raw_materials_based_on", | ||||
|   "capacity_planning", | ||||
|   "disable_capacity_planning", | ||||
|   "allow_overtime", | ||||
|   "allow_production_on_holidays", | ||||
|   "column_break_5", | ||||
|   "capacity_planning_for_days", | ||||
|   "mins_between_operations", | ||||
|   "section_break_6", | ||||
|   "default_wip_warehouse", | ||||
|   "default_fg_warehouse", | ||||
|   "column_break_11", | ||||
|   "default_scrap_warehouse", | ||||
|   "over_production_for_sales_and_work_order_section", | ||||
|   "overproduction_percentage_for_sales_order", | ||||
|   "column_break_16", | ||||
|   "overproduction_percentage_for_work_order", | ||||
|   "other_settings_section", | ||||
|   "update_bom_costs_automatically" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "capacity_planning",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Capacity Planning",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "capacity_planning", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Capacity Planning" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "Disables creation of time logs against Work Orders. Operations shall not be tracked against Work Order",  | ||||
|    "fieldname": "disable_capacity_planning",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Disable Capacity Planning and Time Tracking",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "depends_on": "eval:!doc.disable_capacity_planning", | ||||
|    "description": "Plan time logs outside Workstation Working Hours.", | ||||
|    "fieldname": "allow_overtime", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow Overtime" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "Plan time logs outside Workstation Working Hours.",  | ||||
|    "fieldname": "allow_overtime",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Allow Overtime",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "depends_on": "eval:!doc.disable_capacity_planning", | ||||
|    "fieldname": "allow_production_on_holidays", | ||||
|    "fieldtype": "Check", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Allow Production on Holidays" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "",  | ||||
|    "fieldname": "allow_production_on_holidays",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Allow Production on Holidays",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "column_break_3",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "30", | ||||
|    "depends_on": "eval:!doc.disable_capacity_planning", | ||||
|    "description": "Try planning operations for X days in advance.", | ||||
|    "fieldname": "capacity_planning_for_days", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Capacity Planning For (Days)" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "30",  | ||||
|    "description": "Try planning operations for X days in advance.",  | ||||
|    "fieldname": "capacity_planning_for_days",  | ||||
|    "fieldtype": "Int",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Capacity Planning For (Days)",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "depends_on": "eval:!doc.disable_capacity_planning", | ||||
|    "description": "Default 10 mins", | ||||
|    "fieldname": "mins_between_operations", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Time Between Operations (in mins)" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "Default 10 mins",  | ||||
|    "fieldname": "mins_between_operations",  | ||||
|    "fieldtype": "Int",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Time Between Operations (in mins)",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "section_break_6", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Default Warehouses for Production" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "section_break_6",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "overproduction_percentage_for_sales_order", | ||||
|    "fieldtype": "Percent", | ||||
|    "label": "Overproduction Percentage For Sales Order" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "overproduction_percentage_for_sales_order",  | ||||
|    "fieldtype": "Percent",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Overproduction Percentage For Sales Order",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "overproduction_percentage_for_work_order", | ||||
|    "fieldtype": "Percent", | ||||
|    "label": "Overproduction Percentage For Work Order" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "overproduction_percentage_for_work_order",  | ||||
|    "fieldtype": "Percent",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Overproduction Percentage For Work Order",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "BOM", | ||||
|    "fieldname": "backflush_raw_materials_based_on", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Backflush Raw Materials Based On", | ||||
|    "options": "BOM\nMaterial Transferred for Manufacture" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "BOM",  | ||||
|    "fieldname": "backflush_raw_materials_based_on",  | ||||
|    "fieldtype": "Select",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Backflush Raw Materials Based On",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "BOM\nMaterial Transferred for Manufacture",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "description": "Allow multiple Material Consumption against a Work Order", | ||||
|    "fieldname": "material_consumption", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow Multiple Material Consumption" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "Allow multiple Material Consumption against a Work Order",  | ||||
|    "fieldname": "material_consumption",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Allow Multiple Material Consumption",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.", | ||||
|    "fieldname": "update_bom_costs_automatically", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Update BOM Cost Automatically" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",  | ||||
|    "fieldname": "update_bom_costs_automatically",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Update BOM Cost Automatically",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "column_break_11", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "column_break_11",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "default_wip_warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Default Work In Progress Warehouse", | ||||
|    "options": "Warehouse" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "default_wip_warehouse",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Default Work In Progress Warehouse",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Warehouse",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "default_fg_warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Default Finished Goods Warehouse", | ||||
|    "options": "Warehouse" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "default_fg_warehouse",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Default Finished Goods Warehouse",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Warehouse",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|    "default": "0", | ||||
|    "fieldname": "disable_capacity_planning", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Disable Capacity Planning" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "default_scrap_warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Default Scrap Warehouse", | ||||
|    "options": "Warehouse" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "over_production_for_sales_and_work_order_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Over Production for Sales and Work Order" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "raw_materials_consumption_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Raw Materials Consumption" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_16", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "other_settings_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Other Settings" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_5", | ||||
|    "fieldtype": "Column Break" | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "icon": "icon-wrench",  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 1,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "menu_index": 0,  | ||||
|  "modified": "2018-05-28 00:46:25.310621",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "Manufacturing Settings",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  ], | ||||
|  "icon": "icon-wrench", | ||||
|  "issingle": 1, | ||||
|  "modified": "2019-11-26 13:10:45.569341", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Manufacturing", | ||||
|  "name": "Manufacturing Settings", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 0,  | ||||
|    "email": 0,  | ||||
|    "export": 0,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 0,  | ||||
|    "read": 1,  | ||||
|    "report": 0,  | ||||
|    "role": "Manufacturing Manager",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "create": 1, | ||||
|    "read": 1, | ||||
|    "role": "Manufacturing Manager", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "quick_entry": 0,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -5,10 +5,10 @@ | ||||
| from __future__ import unicode_literals | ||||
| import unittest | ||||
| import frappe | ||||
| from frappe.utils import flt, time_diff_in_hours, now, add_days, cint | ||||
| from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today | ||||
| from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory | ||||
| from erpnext.manufacturing.doctype.work_order.work_order \ | ||||
| 	import make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError | ||||
| from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,  | ||||
| 	ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError) | ||||
| from erpnext.stock.doctype.stock_entry import test_stock_entry | ||||
| from erpnext.stock.utils import get_bin | ||||
| from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order | ||||
| @ -307,14 +307,50 @@ class TestWorkOrder(unittest.TestCase): | ||||
| 			{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) | ||||
| 
 | ||||
| 		if data: | ||||
| 			frappe.db.set_value("Manufacturing Settings", | ||||
| 				None, "disable_capacity_planning", 0) | ||||
| 
 | ||||
| 			bom, bom_item = data | ||||
| 
 | ||||
| 			bom_doc = frappe.get_doc('BOM', bom) | ||||
| 			work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) | ||||
| 			self.assertTrue(work_order.planned_end_date) | ||||
| 
 | ||||
| 			job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}) | ||||
| 			self.assertEqual(len(job_cards), len(bom_doc.operations)) | ||||
| 
 | ||||
| 	def test_capcity_planning(self): | ||||
| 		frappe.db.set_value("Manufacturing Settings", None, { | ||||
| 			"disable_capacity_planning": 0, | ||||
| 			"capacity_planning_for_days": 1 | ||||
| 		}) | ||||
| 
 | ||||
| 		data = frappe.get_cached_value('BOM', {'docstatus': 1, 'item': '_Test FG Item 2', | ||||
| 			'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) | ||||
| 
 | ||||
| 		if data: | ||||
| 			bom, bom_item = data | ||||
| 
 | ||||
| 			planned_start_date = add_months(today(), months=-1) | ||||
| 			work_order = make_wo_order_test_record(item=bom_item, | ||||
| 				qty=10, bom_no=bom, planned_start_date=planned_start_date) | ||||
| 
 | ||||
| 			work_order1 = make_wo_order_test_record(item=bom_item, | ||||
| 				qty=30, bom_no=bom, planned_start_date=planned_start_date, do_not_submit=1) | ||||
| 
 | ||||
| 			self.assertRaises(CapacityError, work_order1.submit) | ||||
| 
 | ||||
| 			frappe.db.set_value("Manufacturing Settings", None, { | ||||
| 				"capacity_planning_for_days": 30 | ||||
| 			}) | ||||
| 
 | ||||
| 			work_order1.reload() | ||||
| 			work_order1.submit() | ||||
| 			self.assertTrue(work_order1.docstatus, 1) | ||||
| 
 | ||||
| 			work_order1.cancel() | ||||
| 			work_order.cancel() | ||||
| 
 | ||||
| 	def test_work_order_with_non_transfer_item(self): | ||||
| 		items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0} | ||||
| 		for item, allow_transfer in items.items(): | ||||
| @ -371,14 +407,12 @@ def make_wo_order_test_record(**args): | ||||
| 	wo_order.skip_transfer=1 | ||||
| 	wo_order.get_items_and_operations_from_bom() | ||||
| 	wo_order.sales_order = args.sales_order or None | ||||
| 	wo_order.planned_start_date = args.planned_start_date or now() | ||||
| 
 | ||||
| 	if args.source_warehouse: | ||||
| 		for item in wo_order.get("required_items"): | ||||
| 			item.source_warehouse = args.source_warehouse | ||||
| 
 | ||||
| 	if args.planned_start_date: | ||||
| 		wo_order.planned_start_date = args.planned_start_date | ||||
| 
 | ||||
| 	if not args.do_not_save: | ||||
| 		wo_order.insert() | ||||
| 
 | ||||
|  | ||||
| @ -551,6 +551,7 @@ erpnext.work_order = { | ||||
| 					if (!r.exe) { | ||||
| 						frm.set_value("wip_warehouse", r.message.wip_warehouse); | ||||
| 						frm.set_value("fg_warehouse", r.message.fg_warehouse); | ||||
| 						frm.set_value("scrap_warehouse", r.message.scrap_warehouse); | ||||
| 					} | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
| @ -12,7 +12,7 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items | ||||
| from dateutil.relativedelta import relativedelta | ||||
| from erpnext.stock.doctype.item.item import validate_end_of_life | ||||
| from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError | ||||
| from erpnext.projects.doctype.timesheet.timesheet import OverlapError | ||||
| from erpnext.manufacturing.doctype.job_card.job_card import OverlapError | ||||
| from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs | ||||
| from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations | ||||
| from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty | ||||
| @ -22,6 +22,7 @@ from erpnext.utilities.transaction_base import validate_uom_is_integer | ||||
| from frappe.model.mapper import get_mapped_doc | ||||
| 
 | ||||
| class OverProductionError(frappe.ValidationError): pass | ||||
| class CapacityError(frappe.ValidationError): pass | ||||
| class StockOverProductionError(frappe.ValidationError): pass | ||||
| class OperationTooLongError(frappe.ValidationError): pass | ||||
| class ItemHasVariantError(frappe.ValidationError): pass | ||||
| @ -260,12 +261,50 @@ class WorkOrder(Document): | ||||
| 		self.update_reserved_qty_for_production() | ||||
| 
 | ||||
| 	def create_job_card(self): | ||||
| 		for row in self.operations: | ||||
| 		manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings") | ||||
| 
 | ||||
| 		enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning) | ||||
| 		plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30 | ||||
| 
 | ||||
| 		for i, row in enumerate(self.operations): | ||||
| 			self.set_operation_start_end_time(i, row) | ||||
| 
 | ||||
| 			if not row.workstation: | ||||
| 				frappe.throw(_("Row {0}: select the workstation against the operation {1}") | ||||
| 					.format(row.idx, row.operation)) | ||||
| 
 | ||||
| 			create_job_card(self, row, auto_create=True) | ||||
| 			original_start_time = row.planned_start_time | ||||
| 			job_card_doc = create_job_card(self, row, | ||||
| 				enable_capacity_planning=enable_capacity_planning, auto_create=True) | ||||
| 
 | ||||
| 			if enable_capacity_planning and job_card_doc: | ||||
| 				row.planned_start_time = job_card_doc.time_logs[0].from_time | ||||
| 				row.planned_end_time = job_card_doc.time_logs[-1].to_time | ||||
| 
 | ||||
| 				if date_diff(row.planned_start_time, original_start_time) > plan_days: | ||||
| 					frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.") | ||||
| 						.format(plan_days, row.operation), CapacityError) | ||||
| 
 | ||||
| 				row.db_update() | ||||
| 
 | ||||
| 		planned_end_date = self.operations and self.operations[-1].planned_end_time | ||||
| 		if planned_end_date: | ||||
| 			self.db_set("planned_end_date", planned_end_date) | ||||
| 
 | ||||
| 	def set_operation_start_end_time(self, idx, row): | ||||
| 		"""Set start and end time for given operation. If first operation, set start as | ||||
| 		`planned_start_date`, else add time diff to end time of earlier operation.""" | ||||
| 		if idx==0: | ||||
| 			# first operation at planned_start date | ||||
| 			row.planned_start_time = self.planned_start_date | ||||
| 		else: | ||||
| 			row.planned_start_time = get_datetime(self.operations[idx-1].planned_end_time)\ | ||||
| 				+ get_mins_between_operations() | ||||
| 
 | ||||
| 		row.planned_end_time = get_datetime(row.planned_start_time) + relativedelta(minutes = row.time_in_mins) | ||||
| 
 | ||||
| 		if row.planned_start_time == row.planned_end_time: | ||||
| 			frappe.throw(_("Capacity Planning Error, planned start time can not be same as end time")) | ||||
| 
 | ||||
| 	def validate_cancel(self): | ||||
| 		if self.status == "Stopped": | ||||
| @ -327,9 +366,8 @@ class WorkOrder(Document): | ||||
| 		"""Fetch operations from BOM and set in 'Work Order'""" | ||||
| 		self.set('operations', []) | ||||
| 
 | ||||
| 		if not self.bom_no \ | ||||
| 			or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")): | ||||
| 				return | ||||
| 		if not self.bom_no: | ||||
| 			return | ||||
| 
 | ||||
| 		if self.use_multi_level_bom: | ||||
| 			bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree() | ||||
| @ -681,11 +719,13 @@ def make_stock_entry(work_order_id, purpose, qty=None): | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_default_warehouse(): | ||||
| 	wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", | ||||
| 		"default_wip_warehouse") | ||||
| 	fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", | ||||
| 		"default_fg_warehouse") | ||||
| 	return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse} | ||||
| 	doc = frappe.get_cached_doc("Manufacturing Settings") | ||||
| 
 | ||||
| 	return { | ||||
| 		"wip_warehouse": doc.default_wip_warehouse, | ||||
| 		"fg_warehouse": doc.default_fg_warehouse, | ||||
| 		"scrap_warehouse": doc.default_scrap_warehouse | ||||
| 	} | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def stop_unstop(work_order, status): | ||||
| @ -721,7 +761,7 @@ def make_job_card(work_order, operation, workstation, qty=0): | ||||
| 	if row: | ||||
| 		return create_job_card(work_order, row, qty) | ||||
| 
 | ||||
| def create_job_card(work_order, row, qty=0, auto_create=False): | ||||
| def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto_create=False): | ||||
| 	doc = frappe.new_doc("Job Card") | ||||
| 	doc.update({ | ||||
| 		'work_order': work_order.name, | ||||
| @ -741,6 +781,9 @@ def create_job_card(work_order, row, qty=0, auto_create=False): | ||||
| 
 | ||||
| 	if auto_create: | ||||
| 		doc.flags.ignore_mandatory = True | ||||
| 		if enable_capacity_planning: | ||||
| 			doc.schedule_time_logs(row) | ||||
| 
 | ||||
| 		doc.insert() | ||||
| 		frappe.msgprint(_("Job card {0} created").format(doc.name)) | ||||
| 
 | ||||
|  | ||||
| @ -1,466 +1,159 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 1,  | ||||
|  "allow_rename": 1,  | ||||
|  "autoname": "field:workstation_name",  | ||||
|  "beta": 0,  | ||||
|  "creation": "2013-01-10 16:34:17",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "Setup",  | ||||
|  "editable_grid": 0,  | ||||
|  "allow_import": 1, | ||||
|  "allow_rename": 1, | ||||
|  "autoname": "field:workstation_name", | ||||
|  "creation": "2013-01-10 16:34:17", | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "Setup", | ||||
|  "field_order": [ | ||||
|   "workstation_name", | ||||
|   "production_capacity", | ||||
|   "column_break_3", | ||||
|   "over_heads", | ||||
|   "hour_rate_electricity", | ||||
|   "hour_rate_consumable", | ||||
|   "column_break_11", | ||||
|   "hour_rate_rent", | ||||
|   "hour_rate_labour", | ||||
|   "hour_rate", | ||||
|   "working_hours_section", | ||||
|   "holiday_list", | ||||
|   "working_hours", | ||||
|   "workstaion_description", | ||||
|   "description" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 1,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "description_section",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Description",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "workstation_name", | ||||
|    "fieldtype": "Data", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Workstation Name", | ||||
|    "oldfieldname": "workstation_name", | ||||
|    "oldfieldtype": "Data", | ||||
|    "reqd": 1, | ||||
|    "unique": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "workstation_name",  | ||||
|    "fieldtype": "Data",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Workstation Name",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "workstation_name",  | ||||
|    "oldfieldtype": "Data",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "description",  | ||||
|    "fieldtype": "Text",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Description",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "description",  | ||||
|    "oldfieldtype": "Text",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0,  | ||||
|    "fieldname": "description", | ||||
|    "fieldtype": "Text", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Description", | ||||
|    "oldfieldname": "description", | ||||
|    "oldfieldtype": "Text", | ||||
|    "width": "300px" | ||||
|   },  | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "over_heads",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Operating Costs",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldtype": "Section Break",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "over_heads", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Operating Costs", | ||||
|    "oldfieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "per hour",  | ||||
|    "fieldname": "hour_rate_electricity",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Electricity Cost",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "hour_rate_electricity",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "bold": 1, | ||||
|    "description": "per hour", | ||||
|    "fieldname": "hour_rate_electricity", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Electricity Cost", | ||||
|    "oldfieldname": "hour_rate_electricity", | ||||
|    "oldfieldtype": "Currency" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "per hour",  | ||||
|    "fieldname": "hour_rate_consumable",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Consumable Cost",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "hour_rate_consumable",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "bold": 1, | ||||
|    "description": "per hour", | ||||
|    "fieldname": "hour_rate_consumable", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Consumable Cost", | ||||
|    "oldfieldname": "hour_rate_consumable", | ||||
|    "oldfieldtype": "Currency" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "column_break_11",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "column_break_11", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "per hour",  | ||||
|    "fieldname": "hour_rate_rent",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Rent Cost",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "hour_rate_rent",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "bold": 1, | ||||
|    "description": "per hour", | ||||
|    "fieldname": "hour_rate_rent", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Rent Cost", | ||||
|    "oldfieldname": "hour_rate_rent", | ||||
|    "oldfieldtype": "Currency" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "Wages per hour",  | ||||
|    "fieldname": "hour_rate_labour",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Wages",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "hour_rate_labour",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "bold": 1, | ||||
|    "description": "Wages per hour", | ||||
|    "fieldname": "hour_rate_labour", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Wages", | ||||
|    "oldfieldname": "hour_rate_labour", | ||||
|    "oldfieldtype": "Currency" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "per hour",  | ||||
|    "fieldname": "hour_rate",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Net Hour Rate",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "hour_rate",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "description": "per hour", | ||||
|    "fieldname": "hour_rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Net Hour Rate", | ||||
|    "oldfieldname": "hour_rate", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "working_hours_section",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Working Hours",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "working_hours_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Working Hours" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "working_hours",  | ||||
|    "fieldtype": "Table",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Working Hours",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Workstation Working Hour",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "working_hours", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Working Hours", | ||||
|    "options": "Workstation Working Hour" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "",  | ||||
|    "fieldname": "holiday_list",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Holiday List",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Holiday List",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|    "fieldname": "holiday_list", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Holiday List", | ||||
|    "options": "Holiday List" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "production_capacity", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Production Capacity", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "workstaion_description", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Description" | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "icon": "icon-wrench",  | ||||
|  "idx": 1,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2017-07-18 22:28:50.163219",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "Workstation",  | ||||
|  "owner": "Administrator",  | ||||
|  ], | ||||
|  "icon": "icon-wrench", | ||||
|  "idx": 1, | ||||
|  "modified": "2019-11-26 12:39:19.742052", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Manufacturing", | ||||
|  "name": "Workstation", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 1,  | ||||
|    "email": 1,  | ||||
|    "export": 0,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Manufacturing User",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Manufacturing User", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 1,  | ||||
|  "sort_order": "ASC",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
|  ], | ||||
|  "quick_entry": 1, | ||||
|  "show_name_in_global_search": 1, | ||||
|  "sort_order": "ASC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -4,7 +4,9 @@ | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils import flt, cint, getdate, formatdate, comma_and, time_diff_in_seconds, to_timedelta | ||||
| from erpnext.support.doctype.issue.issue import get_holidays | ||||
| from frappe.utils import (flt, cint, getdate, formatdate, | ||||
| 	comma_and, time_diff_in_seconds, to_timedelta, add_days) | ||||
| from frappe.model.document import Document | ||||
| from dateutil.parser import parse | ||||
| 
 | ||||
| @ -43,6 +45,17 @@ class Workstation(Document): | ||||
| 				where parent = %s and workstation = %s""", | ||||
| 				(self.hour_rate, bom_no[0], self.name)) | ||||
| 
 | ||||
| 	def validate_workstation_holiday(self, schedule_date, skip_holiday_list_check=False): | ||||
| 		if not skip_holiday_list_check and (not self.holiday_list or | ||||
| 			cint(frappe.db.get_single_value("Manufacturing Settings", "allow_production_on_holidays"))): | ||||
| 			return schedule_date | ||||
| 
 | ||||
| 		if schedule_date in tuple(get_holidays(self.holiday_list)): | ||||
| 			schedule_date = add_days(schedule_date, 1) | ||||
| 			self.validate_workstation_holiday(schedule_date, skip_holiday_list_check=True) | ||||
| 
 | ||||
| 		return schedule_date | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_default_holiday_list(): | ||||
| 	return frappe.get_cached_value('Company',  frappe.defaults.get_user_default("Company"),  "default_holiday_list") | ||||
|  | ||||
| @ -6,8 +6,12 @@ def get_data(): | ||||
| 		'fieldname': 'workstation', | ||||
| 		'transactions': [ | ||||
| 			{ | ||||
| 				'label': _('Manufacture'), | ||||
| 				'items': ['BOM', 'Routing', 'Work Order', 'Job Card', 'Operation', 'Timesheet'] | ||||
| 				'label': _('Master'), | ||||
| 				'items': ['BOM', 'Routing', 'Operation'] | ||||
| 			}, | ||||
| 			{ | ||||
| 				'label': _('Transaction'), | ||||
| 				'items': ['Work Order', 'Job Card', 'Timesheet'] | ||||
| 			} | ||||
| 		] | ||||
| 	} | ||||
|  | ||||
| @ -14,7 +14,7 @@ def execute(filters=None): | ||||
| def get_data(filters, data): | ||||
| 	get_exploded_items(filters.bom, data) | ||||
| 
 | ||||
| def get_exploded_items(bom, data, indent=0): | ||||
| def get_exploded_items(bom, data, indent=0, qty=1): | ||||
| 	exploded_items = frappe.get_all("BOM Item", | ||||
| 		filters={"parent": bom}, | ||||
| 		fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom']) | ||||
| @ -26,13 +26,13 @@ def get_exploded_items(bom, data, indent=0): | ||||
| 			'item_name': item.item_name, | ||||
| 			'indent': indent, | ||||
| 			'bom': item.bom_no, | ||||
| 			'qty': item.qty, | ||||
| 			'qty': item.qty * qty, | ||||
| 			'uom': item.uom, | ||||
| 			'description': item.description, | ||||
| 			'scrap': item.scrap | ||||
| 			}) | ||||
| 		if item.bom_no: | ||||
| 			get_exploded_items(item.bom_no, data, indent=indent+1) | ||||
| 			get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty) | ||||
| 
 | ||||
| def get_columns(): | ||||
| 	return [ | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils.data import comma_and | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| #	if not filters: filters = {} | ||||
| @ -13,35 +14,36 @@ def execute(filters=None): | ||||
| 	data = get_bom_stock(filters) | ||||
| 	qty_to_make = filters.get("qty_to_make") | ||||
| 
 | ||||
| 	manufacture_details = get_manufacturer_records() | ||||
| 	for row in data: | ||||
| 		item_map = get_item_details(row.item_code) | ||||
| 		reqd_qty = qty_to_make * row.actual_qty | ||||
| 		last_pur_price = frappe.db.get_value("Item", row.item_code, "last_purchase_rate") | ||||
| 		if row.to_build > 0: | ||||
| 			diff_qty = row.to_build - reqd_qty | ||||
| 			summ_data.append([row.item_code, row.description, item_map[row.item_code]["manufacturer"], item_map[row.item_code]["manufacturer_part_no"], row.actual_qty, row.to_build, reqd_qty, diff_qty, last_pur_price]) | ||||
| 		else: | ||||
| 			diff_qty = 0 - reqd_qty | ||||
| 			summ_data.append([row.item_code, row.description, item_map[row.item_code]["manufacturer"], item_map[row.item_code]["manufacturer_part_no"], row.actual_qty, "0.000", reqd_qty, diff_qty, last_pur_price]) | ||||
| 
 | ||||
| 		summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details)) | ||||
| 	return columns, summ_data | ||||
| 
 | ||||
| def get_report_data(last_pur_price, reqd_qty, row, manufacture_details): | ||||
| 	to_build = row.to_build if row.to_build > 0 else 0 | ||||
| 	diff_qty = to_build - reqd_qty | ||||
| 	return [row.item_code, row.description, | ||||
| 		comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer', []), add_quotes=False), | ||||
| 		comma_and(manufacture_details.get(row.item_code, {}).get('manufacturer_part', []), add_quotes=False), | ||||
| 		row.actual_qty, str(to_build), | ||||
| 		reqd_qty, diff_qty, last_pur_price] | ||||
| 
 | ||||
| def get_columns(): | ||||
| 	"""return columns""" | ||||
| 	columns = [ | ||||
| 		_("Item") + ":Link/Item:100", | ||||
| 		_("Description") + "::150", | ||||
| 		_("Manufacturer") + "::100", | ||||
| 		_("Manufacturer Part Number") + "::100", | ||||
| 		_("Manufacturer") + "::250", | ||||
| 		_("Manufacturer Part Number") + "::250", | ||||
| 		_("Qty") + ":Float:50", | ||||
| 		_("Stock Qty") + ":Float:100", | ||||
| 		_("Reqd Qty")+ ":Float:100", | ||||
| 		_("Diff Qty")+ ":Float:100", | ||||
| 		_("Last Purchase Price")+ ":Float:100", | ||||
| 
 | ||||
| 
 | ||||
| 	] | ||||
| 
 | ||||
| 	return columns | ||||
| 
 | ||||
| def get_bom_stock(filters): | ||||
| @ -85,7 +87,12 @@ def get_bom_stock(filters): | ||||
| 
 | ||||
| 			GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1) | ||||
| 
 | ||||
| def get_item_details(item_code): | ||||
| 		items = frappe.db.sql("""select it.item_group, it.item_name, it.stock_uom, it.name, it.brand, it.description, it.manufacturer_part_no, it.manufacturer from tabItem it where it.item_code = %s""", item_code, as_dict=1) | ||||
| def get_manufacturer_records(): | ||||
| 	details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no, parent"]) | ||||
| 	manufacture_details = frappe._dict() | ||||
| 	for detail in details: | ||||
| 		dic = manufacture_details.setdefault(detail.get('parent'), {}) | ||||
| 		dic.setdefault('manufacturer', []).append(detail.get('manufacturer')) | ||||
| 		dic.setdefault('manufacturer_part', []).append(detail.get('manufacturer_part_no')) | ||||
| 
 | ||||
| 		return dict((d.name, d) for d in items) | ||||
| 	return manufacture_details | ||||
| @ -647,4 +647,5 @@ erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields | ||||
| erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template | ||||
| erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger | ||||
| erpnext.patches.v12_0.update_price_or_product_discount | ||||
| erpnext.patches.v12_0.set_production_capacity_in_workstation | ||||
| erpnext.patches.v12_0.set_lead_title_field | ||||
|  | ||||
| @ -62,12 +62,12 @@ def execute(): | ||||
| 	] | ||||
| 	 | ||||
| 	for dt in doctypes: | ||||
| 		for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item` | ||||
| 		for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item` | ||||
| 								where ifnull(item_tax_rate, '') not in ('', '{{}}')  | ||||
| 								and item_tax_template is NULL""".format(dt), as_dict=1): | ||||
| 			item_tax_map = json.loads(d.item_tax_rate) | ||||
| 			item_tax_template_name = get_item_tax_template(item_tax_templates, | ||||
| 				item_tax_map, d.item_code, d.parent) | ||||
| 				item_tax_map, d.item_code, d.parenttype, d.parent) | ||||
| 			frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name) | ||||
| 
 | ||||
| 	frappe.db.auto_commit_on_many_writes = False | ||||
| @ -77,7 +77,7 @@ def execute(): | ||||
| 	settings.determine_address_tax_category_from = "Billing Address" | ||||
| 	settings.save() | ||||
| 
 | ||||
| def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None): | ||||
| def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None): | ||||
| 	# search for previously created item tax template by comparing tax maps | ||||
| 	for template, item_tax_template_map in iteritems(item_tax_templates): | ||||
| 		if item_tax_map == item_tax_template_map: | ||||
| @ -88,23 +88,44 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=No | ||||
| 	item_tax_template.title = make_autoname("Item Tax Template-.####") | ||||
| 
 | ||||
| 	for tax_type, tax_rate in iteritems(item_tax_map): | ||||
| 		if not frappe.db.exists("Account", tax_type): | ||||
| 		account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1) | ||||
| 		if account_details: | ||||
| 			if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'): | ||||
| 				frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable') | ||||
| 		else: | ||||
| 			parts = tax_type.strip().split(" - ") | ||||
| 			account_name = " - ".join(parts[:-1]) | ||||
| 			company = frappe.db.get_value("Company", filters={"abbr": parts[-1]}) | ||||
| 			company = get_company(parts[-1], parenttype, parent) | ||||
| 			parent_account = frappe.db.get_value("Account", | ||||
| 				filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account") | ||||
| 
 | ||||
| 			frappe.get_doc({ | ||||
| 				"doctype": "Account", | ||||
| 			filters = { | ||||
| 				"account_name": account_name, | ||||
| 				"company": company, | ||||
| 				"account_type": "Tax", | ||||
| 				"parent_account": parent_account | ||||
| 			}).insert() | ||||
|                                 "company": company, | ||||
|                                 "account_type": "Tax", | ||||
|                                 "parent_account": parent_account | ||||
|                         } | ||||
| 			tax_type = frappe.db.get_value("Account", filters) | ||||
| 			if not tax_type: | ||||
| 				account = frappe.new_doc("Account") | ||||
| 				account.update(filters) | ||||
| 				account.insert() | ||||
| 				tax_type = account.name | ||||
| 
 | ||||
| 		item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) | ||||
| 		item_tax_templates.setdefault(item_tax_template.title, {}) | ||||
| 		item_tax_templates[item_tax_template.title][tax_type] = tax_rate | ||||
| 	item_tax_template.save() | ||||
| 	return item_tax_template.name | ||||
| 
 | ||||
| def get_company(company_abbr, parenttype=None, parent=None): | ||||
| 	if parenttype and parent: | ||||
| 		company = frappe.get_cached_value(parenttype, parent, 'company') | ||||
| 	else: | ||||
| 		company = frappe.db.get_value("Company", filters={"abbr": company_abbr}) | ||||
| 
 | ||||
| 	if not company: | ||||
| 		companies = frappe.get_all('Company') | ||||
| 		if len(companies) == 1: | ||||
| 			company = companies[0].name | ||||
| 
 | ||||
| 	return company | ||||
|  | ||||
| @ -0,0 +1,8 @@ | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| 
 | ||||
| def execute(): | ||||
|     frappe.reload_doc("manufacturing", "doctype", "workstation") | ||||
| 
 | ||||
|     frappe.db.sql(""" UPDATE `tabWorkstation` | ||||
|         SET production_capacity = 1 """) | ||||
| @ -5,6 +5,9 @@ from frappe import _ | ||||
| 
 | ||||
| def execute(): | ||||
| 	"""Add setup progress actions""" | ||||
| 	if not frappe.db.exists('DocType', 'Setup Progress') or not frappe.db.exists('DocType', 'Setup Progress Action'): | ||||
| 		return | ||||
| 
 | ||||
| 	frappe.reload_doc("setup", "doctype", "setup_progress") | ||||
| 	frappe.reload_doc("setup", "doctype", "setup_progress_action") | ||||
| 
 | ||||
|  | ||||
| @ -5,7 +5,7 @@ from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| import unittest | ||||
| from frappe.tests.test_website import set_request | ||||
| from frappe.utils import set_request | ||||
| from frappe.website.render import render | ||||
| 
 | ||||
| class TestHomepage(unittest.TestCase): | ||||
|  | ||||
| @ -6,7 +6,7 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| import unittest | ||||
| from bs4 import BeautifulSoup | ||||
| from frappe.tests.test_website import set_request | ||||
| from frappe.utils import set_request | ||||
| from frappe.website.render import render | ||||
| 
 | ||||
| class TestHomepageSection(unittest.TestCase): | ||||
|  | ||||
| @ -2,7 +2,7 @@ from __future__ import unicode_literals | ||||
| 
 | ||||
| from bs4 import BeautifulSoup | ||||
| import frappe, unittest | ||||
| from frappe.tests.test_website import set_request, get_html_for_route | ||||
| from frappe.utils import set_request, get_html_for_route | ||||
| from frappe.website.render import render | ||||
| from erpnext.portal.product_configurator.utils import get_products_for_website | ||||
| from erpnext.stock.doctype.item.test_item import make_item_variant | ||||
|  | ||||
| @ -313,13 +313,25 @@ def get_items(filters=None, search=None): | ||||
| 
 | ||||
| 	search_condition = '' | ||||
| 	if search: | ||||
| 		# Default fields to search from | ||||
| 		default_fields = {'name', 'item_name', 'description', 'item_group'} | ||||
| 
 | ||||
| 		# Get meta search fields | ||||
| 		meta = frappe.get_meta("Item") | ||||
| 		meta_fields = set(meta.get_search_fields()) | ||||
| 
 | ||||
| 		# Join the meta fields and default fields set | ||||
| 		search_fields = default_fields.union(meta_fields) | ||||
| 		try: | ||||
| 			if frappe.db.count('Item', cache=True) > 50000: | ||||
| 				search_fields.remove('description') | ||||
| 		except KeyError: | ||||
| 			pass | ||||
| 
 | ||||
| 		# Build or filters for query | ||||
| 		search = '%{}%'.format(search) | ||||
| 		or_filters = [ | ||||
| 			['name', 'like', search], | ||||
| 			['item_name', 'like', search], | ||||
| 			['description', 'like', search], | ||||
| 			['item_group', 'like', search] | ||||
| 		] | ||||
| 		or_filters = [[field, 'like', search] for field in search_fields] | ||||
| 
 | ||||
| 		search_condition = get_conditions(or_filters, 'or') | ||||
| 
 | ||||
| 	filter_condition = get_conditions(filters, 'and') | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								erpnext/public/images/illustrations/collaboration.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								erpnext/public/images/illustrations/customer.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								erpnext/public/images/illustrations/letterhead.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								erpnext/public/images/illustrations/onboard.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								erpnext/public/images/illustrations/product.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								erpnext/public/images/illustrations/supplier.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								erpnext/public/images/illustrations/user.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.7 KiB | 
| @ -1716,6 +1716,14 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	against_blanket_order: function(doc, cdt, cdn) { | ||||
| 		var item = locals[cdt][cdn]; | ||||
| 		if(!item.against_blanket_order) { | ||||
| 			frappe.model.set_value(this.frm.doctype + " Item", item.name, "blanket_order", null); | ||||
| 			frappe.model.set_value(this.frm.doctype + " Item", item.name, "blanket_order_rate", 0.00); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	blanket_order: function(doc, cdt, cdn) { | ||||
| 		var me = this; | ||||
| 		var item = locals[cdt][cdn]; | ||||
|  | ||||
| @ -426,7 +426,7 @@ body[data-route="pos"] { | ||||
| 	.collapse-btn { | ||||
| 		cursor: pointer; | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	@media (max-width: @screen-xs) { | ||||
| 		.page-actions { | ||||
| 			max-width: 110px; | ||||
|  | ||||
| @ -3,6 +3,26 @@ | ||||
| 
 | ||||
| frappe.ui.form.on('GST HSN Code', { | ||||
| 	refresh: function(frm) { | ||||
| 
 | ||||
| 		if(! frm.doc.__islocal && frm.doc.taxes.length){ | ||||
| 			frm.add_custom_button(__('Update Taxes for Items'), function(){ | ||||
| 				frappe.confirm( | ||||
| 					'Are you sure? It will overwrite taxes for all items with HSN Code <b>'+frm.doc.name+'</b>.', | ||||
| 					function(){ | ||||
| 						frappe.call({ | ||||
| 							args:{ | ||||
| 								taxes: frm.doc.taxes, | ||||
| 								hsn_code: frm.doc.name | ||||
| 							}, | ||||
| 							method: 'erpnext.regional.doctype.gst_hsn_code.gst_hsn_code.update_taxes_in_item_master', | ||||
| 							callback: function(r) { | ||||
| 								if(r.message){ | ||||
| 									frappe.show_alert(__('Item taxes updated')); | ||||
| 								} | ||||
| 							} | ||||
| 						}); | ||||
| 					} | ||||
| 				); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| }); | ||||
| @ -1,104 +1,46 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "autoname": "field:hsn_code",  | ||||
|  "beta": 0,  | ||||
|  "creation": "2017-06-21 10:48:56.422086",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "autoname": "field:hsn_code", | ||||
|  "creation": "2017-06-21 10:48:56.422086", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "hsn_code", | ||||
|   "description", | ||||
|   "taxes" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "hsn_code",  | ||||
|    "fieldtype": "Data",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "HSN Code",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "hsn_code", | ||||
|    "fieldtype": "Data", | ||||
|    "in_list_view": 1, | ||||
|    "label": "HSN Code", | ||||
|    "reqd": 1, | ||||
|    "unique": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "description",  | ||||
|    "fieldtype": "Small Text",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Description",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|    "fieldname": "description", | ||||
|    "fieldtype": "Small Text", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Description" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "taxes", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Taxes", | ||||
|    "options": "Item Tax" | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2017-09-29 14:38:52.220743",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Regional",  | ||||
|  "name": "GST HSN Code",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "search_fields": "hsn_code, description",  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "title_field": "hsn_code",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
|  ], | ||||
|  "modified": "2019-11-01 11:18:59.556931", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Regional", | ||||
|  "name": "GST HSN Code", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "search_fields": "hsn_code, description", | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "title_field": "hsn_code", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -8,3 +8,22 @@ from frappe.model.document import Document | ||||
| 
 | ||||
| class GSTHSNCode(Document): | ||||
| 	pass | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def update_taxes_in_item_master(taxes, hsn_code): | ||||
| 	items = frappe.get_list("Item", filters={ | ||||
| 		'gst_hsn_code': hsn_code | ||||
| 	}) | ||||
| 
 | ||||
| 	taxes = frappe.parse_json(taxes) | ||||
| 	frappe.enqueue(update_item_document, items=items, taxes=taxes) | ||||
| 	return 1 | ||||
| 
 | ||||
| def update_item_document(items, taxes): | ||||
| 	for item in items: | ||||
| 		item_to_be_updated=frappe.get_doc("Item", item.name) | ||||
| 		item_to_be_updated.taxes = [] | ||||
| 		for tax in taxes: | ||||
| 			tax = frappe._dict(tax) | ||||
| 			item_to_be_updated.append("taxes", {'item_tax_template': tax.item_tax_template, 'tax_category': tax.tax_category}) | ||||
| 			item_to_be_updated.save() | ||||
| @ -10,17 +10,26 @@ Provide a report and downloadable CSV according to the German DATEV format. | ||||
| from __future__ import unicode_literals | ||||
| import datetime | ||||
| import json | ||||
| import zlib | ||||
| import zipfile | ||||
| import six | ||||
| from six import BytesIO | ||||
| from six import string_types | ||||
| import frappe | ||||
| from frappe import _ | ||||
| import pandas as pd | ||||
| from .datev_constants import DataCategory | ||||
| from .datev_constants import Transactions | ||||
| from .datev_constants import DebtorsCreditors | ||||
| from .datev_constants import AccountNames | ||||
| from .datev_constants import QUERY_REPORT_COLUMNS | ||||
| 
 | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| 	"""Entry point for frappe.""" | ||||
| 	validate(filters) | ||||
| 	result = get_gl_entries(filters, as_dict=0) | ||||
| 	columns = get_columns() | ||||
| 	result = get_transactions(filters, as_dict=0) | ||||
| 	columns = QUERY_REPORT_COLUMNS | ||||
| 
 | ||||
| 	return columns, result | ||||
| 
 | ||||
| @ -41,65 +50,8 @@ def validate(filters): | ||||
| 	except frappe.DoesNotExistError: | ||||
| 		frappe.throw(_('Please create <b>DATEV Settings</b> for Company <b>{}</b>.').format(filters.get('company'))) | ||||
| 
 | ||||
| def get_columns(): | ||||
| 	"""Return the list of columns that will be shown in query report.""" | ||||
| 	columns = [ | ||||
| 		{ | ||||
| 			"label": "Umsatz (ohne Soll/Haben-Kz)", | ||||
| 			"fieldname": "Umsatz (ohne Soll/Haben-Kz)", | ||||
| 			"fieldtype": "Currency", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Soll/Haben-Kennzeichen", | ||||
| 			"fieldname": "Soll/Haben-Kennzeichen", | ||||
| 			"fieldtype": "Data", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Kontonummer", | ||||
| 			"fieldname": "Kontonummer", | ||||
| 			"fieldtype": "Data", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Gegenkonto (ohne BU-Schlüssel)", | ||||
| 			"fieldname": "Gegenkonto (ohne BU-Schlüssel)", | ||||
| 			"fieldtype": "Data", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Belegdatum", | ||||
| 			"fieldname": "Belegdatum", | ||||
| 			"fieldtype": "Date", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Buchungstext", | ||||
| 			"fieldname": "Buchungstext", | ||||
| 			"fieldtype": "Text", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Beleginfo - Art 1", | ||||
| 			"fieldname": "Beleginfo - Art 1", | ||||
| 			"fieldtype": "Data", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Beleginfo - Inhalt 1", | ||||
| 			"fieldname": "Beleginfo - Inhalt 1", | ||||
| 			"fieldtype": "Data", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Beleginfo - Art 2", | ||||
| 			"fieldname": "Beleginfo - Art 2", | ||||
| 			"fieldtype": "Data", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": "Beleginfo - Inhalt 2", | ||||
| 			"fieldname": "Beleginfo - Inhalt 2", | ||||
| 			"fieldtype": "Data", | ||||
| 		} | ||||
| 	] | ||||
| 
 | ||||
| 	return columns | ||||
| 
 | ||||
| 
 | ||||
| def get_gl_entries(filters, as_dict): | ||||
| def get_transactions(filters, as_dict=1): | ||||
| 	""" | ||||
| 	Get a list of accounting entries. | ||||
| 
 | ||||
| @ -111,7 +63,7 @@ def get_gl_entries(filters, as_dict): | ||||
| 	as_dict -- return as list of dicts [0,1] | ||||
| 	""" | ||||
| 	gl_entries = frappe.db.sql(""" | ||||
| 		select | ||||
| 		SELECT | ||||
| 
 | ||||
| 			/* either debit or credit amount; always positive */ | ||||
| 			case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)', | ||||
| @ -132,7 +84,7 @@ def get_gl_entries(filters, as_dict): | ||||
| 			gl.against_voucher_type as 'Beleginfo - Art 2', | ||||
| 			gl.against_voucher as 'Beleginfo - Inhalt 2' | ||||
| 
 | ||||
| 		from `tabGL Entry` gl | ||||
| 		FROM `tabGL Entry` gl | ||||
| 
 | ||||
| 			/* Statistisches Konto (Debitoren/Kreditoren) */ | ||||
| 			left join `tabParty Account` pa | ||||
| @ -155,15 +107,127 @@ def get_gl_entries(filters, as_dict): | ||||
| 			left join `tabAccount` acc_against_pa  | ||||
| 			on pa.account = acc_against_pa.name | ||||
| 
 | ||||
| 		where gl.company = %(company)s  | ||||
| 		and DATE(gl.posting_date) >= %(from_date)s | ||||
| 		and DATE(gl.posting_date) <= %(to_date)s | ||||
| 		order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict) | ||||
| 		WHERE gl.company = %(company)s  | ||||
| 		AND DATE(gl.posting_date) >= %(from_date)s | ||||
| 		AND DATE(gl.posting_date) <= %(to_date)s | ||||
| 		ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict, as_utf8=1) | ||||
| 
 | ||||
| 	return gl_entries | ||||
| 
 | ||||
| 
 | ||||
| def get_datev_csv(data, filters): | ||||
| def get_customers(filters): | ||||
| 	""" | ||||
| 	Get a list of Customers. | ||||
| 
 | ||||
| 	Arguments: | ||||
| 	filters -- dict of filters to be passed to the sql query | ||||
| 	""" | ||||
| 	return frappe.db.sql(""" | ||||
| 		SELECT | ||||
| 
 | ||||
| 			acc.account_number as 'Konto', | ||||
| 			cus.customer_name as 'Name (Adressatentyp Unternehmen)', | ||||
| 			case cus.customer_type when 'Individual' then 1 when 'Company' then 2 else 0 end as 'Adressatentyp', | ||||
| 			adr.address_line1 as 'Straße', | ||||
| 			adr.pincode as 'Postleitzahl', | ||||
| 			adr.city as 'Ort', | ||||
| 			UPPER(country.code) as 'Land', | ||||
| 			adr.address_line2 as 'Adresszusatz', | ||||
| 			con.email_id as 'E-Mail', | ||||
| 			coalesce(con.mobile_no, con.phone) as 'Telefon', | ||||
| 			cus.website as 'Internet', | ||||
| 			cus.tax_id as 'Steuernummer', | ||||
| 			ccl.credit_limit as 'Kreditlimit (Debitor)' | ||||
| 
 | ||||
| 		FROM `tabParty Account` par | ||||
| 
 | ||||
| 			left join `tabAccount` acc | ||||
| 			on acc.name = par.account | ||||
| 
 | ||||
| 			left join `tabCustomer` cus | ||||
| 			on cus.name = par.parent | ||||
| 
 | ||||
| 			left join `tabAddress` adr | ||||
| 			on adr.name = cus.customer_primary_address | ||||
| 
 | ||||
| 			left join `tabCountry` country | ||||
| 			on country.name = adr.country | ||||
| 
 | ||||
| 			left join `tabContact` con | ||||
| 			on con.name = cus.customer_primary_contact | ||||
| 
 | ||||
| 			left join `tabCustomer Credit Limit` ccl | ||||
| 			on ccl.parent = cus.name | ||||
| 			and ccl.company = par.company | ||||
| 
 | ||||
| 		WHERE par.company = %(company)s | ||||
| 		AND par.parenttype = 'Customer'""", filters, as_dict=1, as_utf8=1) | ||||
| 
 | ||||
| 
 | ||||
| def get_suppliers(filters): | ||||
| 	""" | ||||
| 	Get a list of Suppliers. | ||||
| 
 | ||||
| 	Arguments: | ||||
| 	filters -- dict of filters to be passed to the sql query | ||||
| 	""" | ||||
| 	return frappe.db.sql(""" | ||||
| 		SELECT | ||||
| 
 | ||||
| 			acc.account_number as 'Konto', | ||||
| 			sup.supplier_name as 'Name (Adressatentyp Unternehmen)', | ||||
| 			case sup.supplier_type when 'Individual' then '1' when 'Company' then '2' else '0' end as 'Adressatentyp', | ||||
| 			adr.address_line1 as 'Straße', | ||||
| 			adr.pincode as 'Postleitzahl', | ||||
| 			adr.city as 'Ort', | ||||
| 			UPPER(country.code) as 'Land', | ||||
| 			adr.address_line2 as 'Adresszusatz', | ||||
| 			con.email_id as 'E-Mail', | ||||
| 			coalesce(con.mobile_no, con.phone) as 'Telefon', | ||||
| 			sup.website as 'Internet', | ||||
| 			sup.tax_id as 'Steuernummer', | ||||
| 			case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis' | ||||
| 
 | ||||
| 		FROM `tabParty Account` par | ||||
| 
 | ||||
| 			left join `tabAccount` acc | ||||
| 			on acc.name = par.account | ||||
| 
 | ||||
| 			left join `tabSupplier` sup | ||||
| 			on sup.name = par.parent | ||||
| 
 | ||||
| 			left join `tabDynamic Link` dyn_adr | ||||
| 			on dyn_adr.link_name = sup.name | ||||
| 			and dyn_adr.link_doctype = 'Supplier' | ||||
| 			and dyn_adr.parenttype = 'Address' | ||||
| 			 | ||||
| 			left join `tabAddress` adr | ||||
| 			on adr.name = dyn_adr.parent | ||||
| 			and adr.is_primary_address = '1' | ||||
| 
 | ||||
| 			left join `tabCountry` country | ||||
| 			on country.name = adr.country | ||||
| 
 | ||||
| 			left join `tabDynamic Link` dyn_con | ||||
| 			on dyn_con.link_name = sup.name | ||||
| 			and dyn_con.link_doctype = 'Supplier' | ||||
| 			and dyn_con.parenttype = 'Contact' | ||||
| 
 | ||||
| 			left join `tabContact` con | ||||
| 			on con.name = dyn_con.parent | ||||
| 			and con.is_primary_contact = '1' | ||||
| 
 | ||||
| 		WHERE par.company = %(company)s | ||||
| 		AND par.parenttype = 'Supplier'""", filters, as_dict=1, as_utf8=1) | ||||
| 
 | ||||
| 
 | ||||
| def get_account_names(filters): | ||||
| 	return frappe.get_list("Account",  | ||||
| 		fields=["account_number as Konto", "name as Kontenbeschriftung"],  | ||||
| 		filters={"company": filters.get("company"), "is_group": "0"}) | ||||
| 
 | ||||
| 
 | ||||
| def get_datev_csv(data, filters, csv_class): | ||||
| 	""" | ||||
| 	Fill in missing columns and return a CSV in DATEV Format. | ||||
| 
 | ||||
| @ -174,7 +238,46 @@ def get_datev_csv(data, filters): | ||||
| 	Arguments: | ||||
| 	data -- array of dictionaries | ||||
| 	filters -- dict | ||||
| 	csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS | ||||
| 	""" | ||||
| 	header = get_header(filters, csv_class) | ||||
| 
 | ||||
| 	empty_df = pd.DataFrame(columns=csv_class.COLUMNS) | ||||
| 	data_df = pd.DataFrame.from_records(data) | ||||
| 
 | ||||
| 	result = empty_df.append(data_df, sort=True) | ||||
| 
 | ||||
| 	if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS: | ||||
| 		result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) | ||||
| 
 | ||||
| 	if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES: | ||||
| 		result['Sprach-ID'] = 'de-DE' | ||||
| 
 | ||||
| 	header = ';'.join(header).encode('latin_1') | ||||
| 	data = result.to_csv( | ||||
| 		# Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035 | ||||
| 		sep=str(';'), | ||||
| 		# European decimal seperator | ||||
| 		decimal=',', | ||||
| 		# Windows "ANSI" encoding | ||||
| 		encoding='latin_1', | ||||
| 		# format date as DDMM | ||||
| 		date_format='%d%m', | ||||
| 		# Windows line terminator | ||||
| 		line_terminator='\r\n', | ||||
| 		# Do not number rows | ||||
| 		index=False, | ||||
| 		# Use all columns defined above | ||||
| 		columns=csv_class.COLUMNS | ||||
| 	) | ||||
| 
 | ||||
| 	if not six.PY2: | ||||
| 		data = data.encode('latin_1') | ||||
| 
 | ||||
| 	return header + b'\r\n' + data | ||||
| 
 | ||||
| 
 | ||||
| def get_header(filters, csv_class): | ||||
| 	header = [ | ||||
| 		# A = DATEV format | ||||
| 		#   DTVF = created by DATEV software, | ||||
| @ -185,18 +288,8 @@ def get_datev_csv(data, filters): | ||||
| 		#   510 = 5.10, | ||||
| 		#   720 = 7.20 | ||||
| 		"510", | ||||
| 		# C = Data category | ||||
| 		#   21 = Transaction batch (Buchungsstapel), | ||||
| 		#   67 = Buchungstextkonstanten, | ||||
| 		#   16 = Debitors/Creditors, | ||||
| 		#   20 = Account names (Kontenbeschriftungen) | ||||
| 		"21", | ||||
| 		# D = Format name | ||||
| 		#   Buchungsstapel, | ||||
| 		#   Buchungstextkonstanten, | ||||
| 		#   Debitoren/Kreditoren, | ||||
| 		#   Kontenbeschriftungen | ||||
| 		"Buchungsstapel", | ||||
| 		csv_class.DATA_CATEGORY, | ||||
| 		csv_class.FORMAT_NAME, | ||||
| 		# E = Format version (regarding format name) | ||||
| 		"", | ||||
| 		# F = Generated on | ||||
| @ -224,16 +317,17 @@ def get_datev_csv(data, filters): | ||||
| 		# P = Transaction batch end date (YYYYMMDD) | ||||
| 		frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"), | ||||
| 		# Q = Description (for example, "January - February 2019 Transactions") | ||||
| 		"{} - {} Buchungsstapel".format( | ||||
| 			frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"), | ||||
| 			frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy") | ||||
| 		"{} - {} {}".format( | ||||
| 				frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"), | ||||
| 				frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy"), | ||||
| 				csv_class.FORMAT_NAME | ||||
| 		), | ||||
| 		# R = Diktatkürzel | ||||
| 		"", | ||||
| 		# S = Buchungstyp | ||||
| 		#   1 = Transaction batch (Buchungsstapel), | ||||
| 		#   2 = Annual financial statement (Jahresabschluss) | ||||
| 		"1", | ||||
| 		"1" if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "", | ||||
| 		# T = Rechnungslegungszweck | ||||
| 		"", | ||||
| 		# U = Festschreibung | ||||
| @ -241,185 +335,8 @@ def get_datev_csv(data, filters): | ||||
| 		# V = Kontoführungs-Währungskennzeichen des Geldkontos | ||||
| 		frappe.get_value("Company", filters.get("company"), "default_currency") | ||||
| 	] | ||||
| 	columns = [ | ||||
| 		# All possible columns must tbe listed here, because DATEV requires them to | ||||
| 		# be present in the CSV. | ||||
| 		# --- | ||||
| 		# Umsatz | ||||
| 		"Umsatz (ohne Soll/Haben-Kz)", | ||||
| 		"Soll/Haben-Kennzeichen", | ||||
| 		"WKZ Umsatz", | ||||
| 		"Kurs", | ||||
| 		"Basis-Umsatz", | ||||
| 		"WKZ Basis-Umsatz", | ||||
| 		# Konto/Gegenkonto | ||||
| 		"Kontonummer", | ||||
| 		"Gegenkonto (ohne BU-Schlüssel)", | ||||
| 		"BU-Schlüssel", | ||||
| 		# Datum | ||||
| 		"Belegdatum", | ||||
| 		# Belegfelder | ||||
| 		"Belegfeld 1", | ||||
| 		"Belegfeld 2", | ||||
| 		# Weitere Felder | ||||
| 		"Skonto", | ||||
| 		"Buchungstext", | ||||
| 		# OPOS-Informationen | ||||
| 		"Postensperre", | ||||
| 		"Diverse Adressnummer", | ||||
| 		"Geschäftspartnerbank", | ||||
| 		"Sachverhalt", | ||||
| 		"Zinssperre", | ||||
| 		# Digitaler Beleg | ||||
| 		"Beleglink", | ||||
| 		# Beleginfo | ||||
| 		"Beleginfo - Art 1", | ||||
| 		"Beleginfo - Inhalt 1", | ||||
| 		"Beleginfo - Art 2", | ||||
| 		"Beleginfo - Inhalt 2", | ||||
| 		"Beleginfo - Art 3", | ||||
| 		"Beleginfo - Inhalt 3", | ||||
| 		"Beleginfo - Art 4", | ||||
| 		"Beleginfo - Inhalt 4", | ||||
| 		"Beleginfo - Art 5", | ||||
| 		"Beleginfo - Inhalt 5", | ||||
| 		"Beleginfo - Art 6", | ||||
| 		"Beleginfo - Inhalt 6", | ||||
| 		"Beleginfo - Art 7", | ||||
| 		"Beleginfo - Inhalt 7", | ||||
| 		"Beleginfo - Art 8", | ||||
| 		"Beleginfo - Inhalt 8", | ||||
| 		# Kostenrechnung | ||||
| 		"Kost 1 - Kostenstelle", | ||||
| 		"Kost 2 - Kostenstelle", | ||||
| 		"Kost-Menge", | ||||
| 		# Steuerrechnung | ||||
| 		"EU-Land u. UStID", | ||||
| 		"EU-Steuersatz", | ||||
| 		"Abw. Versteuerungsart", | ||||
| 		# L+L Sachverhalt | ||||
| 		"Sachverhalt L+L", | ||||
| 		"Funktionsergänzung L+L", | ||||
| 		# Funktion Steuerschlüssel 49 | ||||
| 		"BU 49 Hauptfunktionstyp", | ||||
| 		"BU 49 Hauptfunktionsnummer", | ||||
| 		"BU 49 Funktionsergänzung", | ||||
| 		# Zusatzinformationen | ||||
| 		"Zusatzinformation - Art 1", | ||||
| 		"Zusatzinformation - Inhalt 1", | ||||
| 		"Zusatzinformation - Art 2", | ||||
| 		"Zusatzinformation - Inhalt 2", | ||||
| 		"Zusatzinformation - Art 3", | ||||
| 		"Zusatzinformation - Inhalt 3", | ||||
| 		"Zusatzinformation - Art 4", | ||||
| 		"Zusatzinformation - Inhalt 4", | ||||
| 		"Zusatzinformation - Art 5", | ||||
| 		"Zusatzinformation - Inhalt 5", | ||||
| 		"Zusatzinformation - Art 6", | ||||
| 		"Zusatzinformation - Inhalt 6", | ||||
| 		"Zusatzinformation - Art 7", | ||||
| 		"Zusatzinformation - Inhalt 7", | ||||
| 		"Zusatzinformation - Art 8", | ||||
| 		"Zusatzinformation - Inhalt 8", | ||||
| 		"Zusatzinformation - Art 9", | ||||
| 		"Zusatzinformation - Inhalt 9", | ||||
| 		"Zusatzinformation - Art 10", | ||||
| 		"Zusatzinformation - Inhalt 10", | ||||
| 		"Zusatzinformation - Art 11", | ||||
| 		"Zusatzinformation - Inhalt 11", | ||||
| 		"Zusatzinformation - Art 12", | ||||
| 		"Zusatzinformation - Inhalt 12", | ||||
| 		"Zusatzinformation - Art 13", | ||||
| 		"Zusatzinformation - Inhalt 13", | ||||
| 		"Zusatzinformation - Art 14", | ||||
| 		"Zusatzinformation - Inhalt 14", | ||||
| 		"Zusatzinformation - Art 15", | ||||
| 		"Zusatzinformation - Inhalt 15", | ||||
| 		"Zusatzinformation - Art 16", | ||||
| 		"Zusatzinformation - Inhalt 16", | ||||
| 		"Zusatzinformation - Art 17", | ||||
| 		"Zusatzinformation - Inhalt 17", | ||||
| 		"Zusatzinformation - Art 18", | ||||
| 		"Zusatzinformation - Inhalt 18", | ||||
| 		"Zusatzinformation - Art 19", | ||||
| 		"Zusatzinformation - Inhalt 19", | ||||
| 		"Zusatzinformation - Art 20", | ||||
| 		"Zusatzinformation - Inhalt 20", | ||||
| 		# Mengenfelder LuF | ||||
| 		"Stück", | ||||
| 		"Gewicht", | ||||
| 		# Forderungsart | ||||
| 		"Zahlweise", | ||||
| 		"Forderungsart", | ||||
| 		"Veranlagungsjahr", | ||||
| 		"Zugeordnete Fälligkeit", | ||||
| 		# Weitere Felder | ||||
| 		"Skontotyp", | ||||
| 		# Anzahlungen | ||||
| 		"Auftragsnummer", | ||||
| 		"Buchungstyp", | ||||
| 		"USt-Schlüssel (Anzahlungen)", | ||||
| 		"EU-Land (Anzahlungen)", | ||||
| 		"Sachverhalt L+L (Anzahlungen)", | ||||
| 		"EU-Steuersatz (Anzahlungen)", | ||||
| 		"Erlöskonto (Anzahlungen)", | ||||
| 		# Stapelinformationen | ||||
| 		"Herkunft-Kz", | ||||
| 		# Technische Identifikation | ||||
| 		"Buchungs GUID", | ||||
| 		# Kostenrechnung | ||||
| 		"Kost-Datum", | ||||
| 		# OPOS-Informationen | ||||
| 		"SEPA-Mandatsreferenz", | ||||
| 		"Skontosperre", | ||||
| 		# Gesellschafter und Sonderbilanzsachverhalt | ||||
| 		"Gesellschaftername", | ||||
| 		"Beteiligtennummer", | ||||
| 		"Identifikationsnummer", | ||||
| 		"Zeichnernummer", | ||||
| 		# OPOS-Informationen | ||||
| 		"Postensperre bis", | ||||
| 		# Gesellschafter und Sonderbilanzsachverhalt | ||||
| 		"Bezeichnung SoBil-Sachverhalt", | ||||
| 		"Kennzeichen SoBil-Buchung", | ||||
| 		# Stapelinformationen | ||||
| 		"Festschreibung", | ||||
| 		# Datum | ||||
| 		"Leistungsdatum", | ||||
| 		"Datum Zuord. Steuerperiode", | ||||
| 		# OPOS-Informationen | ||||
| 		"Fälligkeit", | ||||
| 		# Konto/Gegenkonto | ||||
| 		"Generalumkehr (GU)", | ||||
| 		# Steuersatz für Steuerschlüssel | ||||
| 		"Steuersatz", | ||||
| 		"Land" | ||||
| 	] | ||||
| 	return header | ||||
| 
 | ||||
| 	empty_df = pd.DataFrame(columns=columns) | ||||
| 	data_df = pd.DataFrame.from_records(data) | ||||
| 
 | ||||
| 	result = empty_df.append(data_df) | ||||
| 	result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) | ||||
| 
 | ||||
| 	header = ';'.join(header).encode('latin_1') | ||||
| 	data = result.to_csv( | ||||
| 		sep=b';', | ||||
| 		# European decimal seperator | ||||
| 		decimal=',', | ||||
| 		# Windows "ANSI" encoding | ||||
| 		encoding='latin_1', | ||||
| 		# format date as DDMM | ||||
| 		date_format='%d%m', | ||||
| 		# Windows line terminator | ||||
| 		line_terminator=b'\r\n', | ||||
| 		# Do not number rows | ||||
| 		index=False, | ||||
| 		# Use all columns defined above | ||||
| 		columns=columns | ||||
| 	) | ||||
| 
 | ||||
| 	return header + b'\r\n' + data | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def download_datev_csv(filters=None): | ||||
| @ -438,8 +355,31 @@ def download_datev_csv(filters=None): | ||||
| 		filters = json.loads(filters) | ||||
| 
 | ||||
| 	validate(filters) | ||||
| 	data = get_gl_entries(filters, as_dict=1) | ||||
| 
 | ||||
| 	frappe.response['result'] = get_datev_csv(data, filters) | ||||
| 	frappe.response['doctype'] = 'EXTF_Buchungsstapel' | ||||
| 	frappe.response['type'] = 'csv' | ||||
| 	# This is where my zip will be written | ||||
| 	zip_buffer = BytesIO() | ||||
| 	# This is my zip file | ||||
| 	datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED) | ||||
| 
 | ||||
| 	transactions = get_transactions(filters) | ||||
| 	transactions_csv = get_datev_csv(transactions, filters, csv_class=Transactions) | ||||
| 	datev_zip.writestr('EXTF_Buchungsstapel.csv', transactions_csv) | ||||
| 
 | ||||
| 	account_names = get_account_names(filters) | ||||
| 	account_names_csv = get_datev_csv(account_names, filters, csv_class=AccountNames) | ||||
| 	datev_zip.writestr('EXTF_Kontenbeschriftungen.csv', account_names_csv) | ||||
| 
 | ||||
| 	customers = get_customers(filters) | ||||
| 	customers_csv = get_datev_csv(customers, filters, csv_class=DebtorsCreditors) | ||||
| 	datev_zip.writestr('EXTF_Kunden.csv', customers_csv) | ||||
| 
 | ||||
| 	suppliers = get_suppliers(filters) | ||||
| 	suppliers_csv = get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors) | ||||
| 	datev_zip.writestr('EXTF_Lieferanten.csv', suppliers_csv) | ||||
| 	 | ||||
| 	# You must call close() before exiting your program or essential records will not be written. | ||||
| 	datev_zip.close() | ||||
| 
 | ||||
| 	frappe.response['filecontent'] = zip_buffer.getvalue() | ||||
| 	frappe.response['filename'] = 'DATEV.zip' | ||||
| 	frappe.response['type'] = 'binary' | ||||
|  | ||||
							
								
								
									
										512
									
								
								erpnext/regional/report/datev/datev_constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,512 @@ | ||||
| # coding: utf-8 | ||||
| """Constants used in datev.py.""" | ||||
| 
 | ||||
| TRANSACTION_COLUMNS = [ | ||||
| 	# All possible columns must tbe listed here, because DATEV requires them to | ||||
| 	# be present in the CSV. | ||||
| 	# --- | ||||
| 	# Umsatz | ||||
| 	"Umsatz (ohne Soll/Haben-Kz)", | ||||
| 	"Soll/Haben-Kennzeichen", | ||||
| 	"WKZ Umsatz", | ||||
| 	"Kurs", | ||||
| 	"Basis-Umsatz", | ||||
| 	"WKZ Basis-Umsatz", | ||||
| 	# Konto/Gegenkonto | ||||
| 	"Kontonummer", | ||||
| 	"Gegenkonto (ohne BU-Schlüssel)", | ||||
| 	"BU-Schlüssel", | ||||
| 	# Datum | ||||
| 	"Belegdatum", | ||||
| 	# Belegfelder | ||||
| 	"Belegfeld 1", | ||||
| 	"Belegfeld 2", | ||||
| 	# Weitere Felder | ||||
| 	"Skonto", | ||||
| 	"Buchungstext", | ||||
| 	# OPOS-Informationen | ||||
| 	"Postensperre", | ||||
| 	"Diverse Adressnummer", | ||||
| 	"Geschäftspartnerbank", | ||||
| 	"Sachverhalt", | ||||
| 	"Zinssperre", | ||||
| 	# Digitaler Beleg | ||||
| 	"Beleglink", | ||||
| 	# Beleginfo | ||||
| 	"Beleginfo - Art 1", | ||||
| 	"Beleginfo - Inhalt 1", | ||||
| 	"Beleginfo - Art 2", | ||||
| 	"Beleginfo - Inhalt 2", | ||||
| 	"Beleginfo - Art 3", | ||||
| 	"Beleginfo - Inhalt 3", | ||||
| 	"Beleginfo - Art 4", | ||||
| 	"Beleginfo - Inhalt 4", | ||||
| 	"Beleginfo - Art 5", | ||||
| 	"Beleginfo - Inhalt 5", | ||||
| 	"Beleginfo - Art 6", | ||||
| 	"Beleginfo - Inhalt 6", | ||||
| 	"Beleginfo - Art 7", | ||||
| 	"Beleginfo - Inhalt 7", | ||||
| 	"Beleginfo - Art 8", | ||||
| 	"Beleginfo - Inhalt 8", | ||||
| 	# Kostenrechnung | ||||
| 	"Kost 1 - Kostenstelle", | ||||
| 	"Kost 2 - Kostenstelle", | ||||
| 	"Kost-Menge", | ||||
| 	# Steuerrechnung | ||||
| 	"EU-Land u. UStID", | ||||
| 	"EU-Steuersatz", | ||||
| 	"Abw. Versteuerungsart", | ||||
| 	# L+L Sachverhalt | ||||
| 	"Sachverhalt L+L", | ||||
| 	"Funktionsergänzung L+L", | ||||
| 	# Funktion Steuerschlüssel 49 | ||||
| 	"BU 49 Hauptfunktionstyp", | ||||
| 	"BU 49 Hauptfunktionsnummer", | ||||
| 	"BU 49 Funktionsergänzung", | ||||
| 	# Zusatzinformationen | ||||
| 	"Zusatzinformation - Art 1", | ||||
| 	"Zusatzinformation - Inhalt 1", | ||||
| 	"Zusatzinformation - Art 2", | ||||
| 	"Zusatzinformation - Inhalt 2", | ||||
| 	"Zusatzinformation - Art 3", | ||||
| 	"Zusatzinformation - Inhalt 3", | ||||
| 	"Zusatzinformation - Art 4", | ||||
| 	"Zusatzinformation - Inhalt 4", | ||||
| 	"Zusatzinformation - Art 5", | ||||
| 	"Zusatzinformation - Inhalt 5", | ||||
| 	"Zusatzinformation - Art 6", | ||||
| 	"Zusatzinformation - Inhalt 6", | ||||
| 	"Zusatzinformation - Art 7", | ||||
| 	"Zusatzinformation - Inhalt 7", | ||||
| 	"Zusatzinformation - Art 8", | ||||
| 	"Zusatzinformation - Inhalt 8", | ||||
| 	"Zusatzinformation - Art 9", | ||||
| 	"Zusatzinformation - Inhalt 9", | ||||
| 	"Zusatzinformation - Art 10", | ||||
| 	"Zusatzinformation - Inhalt 10", | ||||
| 	"Zusatzinformation - Art 11", | ||||
| 	"Zusatzinformation - Inhalt 11", | ||||
| 	"Zusatzinformation - Art 12", | ||||
| 	"Zusatzinformation - Inhalt 12", | ||||
| 	"Zusatzinformation - Art 13", | ||||
| 	"Zusatzinformation - Inhalt 13", | ||||
| 	"Zusatzinformation - Art 14", | ||||
| 	"Zusatzinformation - Inhalt 14", | ||||
| 	"Zusatzinformation - Art 15", | ||||
| 	"Zusatzinformation - Inhalt 15", | ||||
| 	"Zusatzinformation - Art 16", | ||||
| 	"Zusatzinformation - Inhalt 16", | ||||
| 	"Zusatzinformation - Art 17", | ||||
| 	"Zusatzinformation - Inhalt 17", | ||||
| 	"Zusatzinformation - Art 18", | ||||
| 	"Zusatzinformation - Inhalt 18", | ||||
| 	"Zusatzinformation - Art 19", | ||||
| 	"Zusatzinformation - Inhalt 19", | ||||
| 	"Zusatzinformation - Art 20", | ||||
| 	"Zusatzinformation - Inhalt 20", | ||||
| 	# Mengenfelder LuF | ||||
| 	"Stück", | ||||
| 	"Gewicht", | ||||
| 	# Forderungsart | ||||
| 	"Zahlweise", | ||||
| 	"Forderungsart", | ||||
| 	"Veranlagungsjahr", | ||||
| 	"Zugeordnete Fälligkeit", | ||||
| 	# Weitere Felder | ||||
| 	"Skontotyp", | ||||
| 	# Anzahlungen | ||||
| 	"Auftragsnummer", | ||||
| 	"Buchungstyp", | ||||
| 	"USt-Schlüssel (Anzahlungen)", | ||||
| 	"EU-Land (Anzahlungen)", | ||||
| 	"Sachverhalt L+L (Anzahlungen)", | ||||
| 	"EU-Steuersatz (Anzahlungen)", | ||||
| 	"Erlöskonto (Anzahlungen)", | ||||
| 	# Stapelinformationen | ||||
| 	"Herkunft-Kz", | ||||
| 	# Technische Identifikation | ||||
| 	"Buchungs GUID", | ||||
| 	# Kostenrechnung | ||||
| 	"Kost-Datum", | ||||
| 	# OPOS-Informationen | ||||
| 	"SEPA-Mandatsreferenz", | ||||
| 	"Skontosperre", | ||||
| 	# Gesellschafter und Sonderbilanzsachverhalt | ||||
| 	"Gesellschaftername", | ||||
| 	"Beteiligtennummer", | ||||
| 	"Identifikationsnummer", | ||||
| 	"Zeichnernummer", | ||||
| 	# OPOS-Informationen | ||||
| 	"Postensperre bis", | ||||
| 	# Gesellschafter und Sonderbilanzsachverhalt | ||||
| 	"Bezeichnung SoBil-Sachverhalt", | ||||
| 	"Kennzeichen SoBil-Buchung", | ||||
| 	# Stapelinformationen | ||||
| 	"Festschreibung", | ||||
| 	# Datum | ||||
| 	"Leistungsdatum", | ||||
| 	"Datum Zuord. Steuerperiode", | ||||
| 	# OPOS-Informationen | ||||
| 	"Fälligkeit", | ||||
| 	# Konto/Gegenkonto | ||||
| 	"Generalumkehr (GU)", | ||||
| 	# Steuersatz für Steuerschlüssel | ||||
| 	"Steuersatz", | ||||
| 	"Land" | ||||
| ] | ||||
| 
 | ||||
| DEBTOR_CREDITOR_COLUMNS = [ | ||||
| 	# All possible columns must tbe listed here, because DATEV requires them to | ||||
| 	# be present in the CSV. | ||||
| 	# Columns "Leerfeld" have been replaced with "Leerfeld #" to not confuse pandas | ||||
| 	# --- | ||||
| 	"Konto", | ||||
| 	"Name (Adressatentyp Unternehmen)", | ||||
| 	"Unternehmensgegenstand", | ||||
| 	"Name (Adressatentyp natürl. Person)", | ||||
| 	"Vorname (Adressatentyp natürl. Person)", | ||||
| 	"Name (Adressatentyp keine Angabe)", | ||||
| 	"Adressatentyp", | ||||
| 	"Kurzbezeichnung", | ||||
| 	"EU-Land", | ||||
| 	"EU-USt-IdNr.", | ||||
| 	"Anrede", | ||||
| 	"Titel/Akad. Grad", | ||||
| 	"Adelstitel", | ||||
| 	"Namensvorsatz", | ||||
| 	"Adressart", | ||||
| 	"Straße", | ||||
| 	"Postfach", | ||||
| 	"Postleitzahl", | ||||
| 	"Ort", | ||||
| 	"Land", | ||||
| 	"Versandzusatz", | ||||
| 	"Adresszusatz", | ||||
| 	"Abweichende Anrede", | ||||
| 	"Abw. Zustellbezeichnung 1", | ||||
| 	"Abw. Zustellbezeichnung 2", | ||||
| 	"Kennz. Korrespondenzadresse", | ||||
| 	"Adresse gültig von", | ||||
| 	"Adresse gültig bis", | ||||
| 	"Telefon", | ||||
| 	"Bemerkung (Telefon)", | ||||
| 	"Telefon Geschäftsleitung", | ||||
| 	"Bemerkung (Telefon GL)", | ||||
| 	"E-Mail", | ||||
| 	"Bemerkung (E-Mail)", | ||||
| 	"Internet", | ||||
| 	"Bemerkung (Internet)", | ||||
| 	"Fax", | ||||
| 	"Bemerkung (Fax)", | ||||
| 	"Sonstige", | ||||
| 	"Bemerkung (Sonstige)", | ||||
| 	"Bankleitzahl 1", | ||||
| 	"Bankbezeichnung 1", | ||||
| 	"Bankkonto-Nummer 1", | ||||
| 	"Länderkennzeichen 1", | ||||
| 	"IBAN 1", | ||||
| 	"Leerfeld 1", | ||||
| 	"SWIFT-Code 1", | ||||
| 	"Abw. Kontoinhaber 1", | ||||
| 	"Kennz. Haupt-Bankverb. 1", | ||||
| 	"Bankverb. 1 Gültig von", | ||||
| 	"Bankverb. 1 Gültig bis", | ||||
| 	"Bankleitzahl 2", | ||||
| 	"Bankbezeichnung 2", | ||||
| 	"Bankkonto-Nummer 2", | ||||
| 	"Länderkennzeichen 2", | ||||
| 	"IBAN 2", | ||||
| 	"Leerfeld 2", | ||||
| 	"SWIFT-Code 2", | ||||
| 	"Abw. Kontoinhaber 2", | ||||
| 	"Kennz. Haupt-Bankverb. 2", | ||||
| 	"Bankverb. 2 gültig von", | ||||
| 	"Bankverb. 2 gültig bis", | ||||
| 	"Bankleitzahl 3", | ||||
| 	"Bankbezeichnung 3", | ||||
| 	"Bankkonto-Nummer 3", | ||||
| 	"Länderkennzeichen 3", | ||||
| 	"IBAN 3", | ||||
| 	"Leerfeld 3", | ||||
| 	"SWIFT-Code 3", | ||||
| 	"Abw. Kontoinhaber 3", | ||||
| 	"Kennz. Haupt-Bankverb. 3", | ||||
| 	"Bankverb. 3 gültig von", | ||||
| 	"Bankverb. 3 gültig bis", | ||||
| 	"Bankleitzahl 4", | ||||
| 	"Bankbezeichnung 4", | ||||
| 	"Bankkonto-Nummer 4", | ||||
| 	"Länderkennzeichen 4", | ||||
| 	"IBAN 4", | ||||
| 	"Leerfeld 4", | ||||
| 	"SWIFT-Code 4", | ||||
| 	"Abw. Kontoinhaber 4", | ||||
| 	"Kennz. Haupt-Bankverb. 4", | ||||
| 	"Bankverb. 4 Gültig von", | ||||
| 	"Bankverb. 4 Gültig bis", | ||||
| 	"Bankleitzahl 5", | ||||
| 	"Bankbezeichnung 5", | ||||
| 	"Bankkonto-Nummer 5", | ||||
| 	"Länderkennzeichen 5", | ||||
| 	"IBAN 5", | ||||
| 	"Leerfeld 5", | ||||
| 	"SWIFT-Code 5", | ||||
| 	"Abw. Kontoinhaber 5", | ||||
| 	"Kennz. Haupt-Bankverb. 5", | ||||
| 	"Bankverb. 5 gültig von", | ||||
| 	"Bankverb. 5 gültig bis", | ||||
| 	"Leerfeld 6", | ||||
| 	"Briefanrede", | ||||
| 	"Grußformel", | ||||
| 	"Kundennummer", | ||||
| 	"Steuernummer", | ||||
| 	"Sprache", | ||||
| 	"Ansprechpartner", | ||||
| 	"Vertreter", | ||||
| 	"Sachbearbeiter", | ||||
| 	"Diverse-Konto", | ||||
| 	"Ausgabeziel", | ||||
| 	"Währungssteuerung", | ||||
| 	"Kreditlimit (Debitor)", | ||||
| 	"Zahlungsbedingung", | ||||
| 	"Fälligkeit in Tagen (Debitor)", | ||||
| 	"Skonto in Prozent (Debitor)", | ||||
| 	"Kreditoren-Ziel 1 (Tage)", | ||||
| 	"Kreditoren-Skonto 1 (%)", | ||||
| 	"Kreditoren-Ziel 2 (Tage)", | ||||
| 	"Kreditoren-Skonto 2 (%)", | ||||
| 	"Kreditoren-Ziel 3 Brutto (Tage)", | ||||
| 	"Kreditoren-Ziel 4 (Tage)", | ||||
| 	"Kreditoren-Skonto 4 (%)", | ||||
| 	"Kreditoren-Ziel 5 (Tage)", | ||||
| 	"Kreditoren-Skonto 5 (%)", | ||||
| 	"Mahnung", | ||||
| 	"Kontoauszug", | ||||
| 	"Mahntext 1", | ||||
| 	"Mahntext 2", | ||||
| 	"Mahntext 3", | ||||
| 	"Kontoauszugstext", | ||||
| 	"Mahnlimit Betrag", | ||||
| 	"Mahnlimit %", | ||||
| 	"Zinsberechnung", | ||||
| 	"Mahnzinssatz 1", | ||||
| 	"Mahnzinssatz 2", | ||||
| 	"Mahnzinssatz 3", | ||||
| 	"Lastschrift", | ||||
| 	"Verfahren", | ||||
| 	"Mandantenbank", | ||||
| 	"Zahlungsträger", | ||||
| 	"Indiv. Feld 1", | ||||
| 	"Indiv. Feld 2", | ||||
| 	"Indiv. Feld 3", | ||||
| 	"Indiv. Feld 4", | ||||
| 	"Indiv. Feld 5", | ||||
| 	"Indiv. Feld 6", | ||||
| 	"Indiv. Feld 7", | ||||
| 	"Indiv. Feld 8", | ||||
| 	"Indiv. Feld 9", | ||||
| 	"Indiv. Feld 10", | ||||
| 	"Indiv. Feld 11", | ||||
| 	"Indiv. Feld 12", | ||||
| 	"Indiv. Feld 13", | ||||
| 	"Indiv. Feld 14", | ||||
| 	"Indiv. Feld 15", | ||||
| 	"Abweichende Anrede (Rechnungsadresse)", | ||||
| 	"Adressart (Rechnungsadresse)", | ||||
| 	"Straße (Rechnungsadresse)", | ||||
| 	"Postfach (Rechnungsadresse)", | ||||
| 	"Postleitzahl (Rechnungsadresse)", | ||||
| 	"Ort (Rechnungsadresse)", | ||||
| 	"Land (Rechnungsadresse)", | ||||
| 	"Versandzusatz (Rechnungsadresse)", | ||||
| 	"Adresszusatz (Rechnungsadresse)", | ||||
| 	"Abw. Zustellbezeichnung 1 (Rechnungsadresse)", | ||||
| 	"Abw. Zustellbezeichnung 2 (Rechnungsadresse)", | ||||
| 	"Adresse Gültig von (Rechnungsadresse)", | ||||
| 	"Adresse Gültig bis (Rechnungsadresse)", | ||||
| 	"Bankleitzahl 6", | ||||
| 	"Bankbezeichnung 6", | ||||
| 	"Bankkonto-Nummer 6", | ||||
| 	"Länderkennzeichen 6", | ||||
| 	"IBAN 6", | ||||
| 	"Leerfeld 7", | ||||
| 	"SWIFT-Code 6", | ||||
| 	"Abw. Kontoinhaber 6", | ||||
| 	"Kennz. Haupt-Bankverb. 6", | ||||
| 	"Bankverb 6 gültig von", | ||||
| 	"Bankverb 6 gültig bis", | ||||
| 	"Bankleitzahl 7", | ||||
| 	"Bankbezeichnung 7", | ||||
| 	"Bankkonto-Nummer 7", | ||||
| 	"Länderkennzeichen 7", | ||||
| 	"IBAN 7", | ||||
| 	"Leerfeld 8", | ||||
| 	"SWIFT-Code 7", | ||||
| 	"Abw. Kontoinhaber 7", | ||||
| 	"Kennz. Haupt-Bankverb. 7", | ||||
| 	"Bankverb 7 gültig von", | ||||
| 	"Bankverb 7 gültig bis", | ||||
| 	"Bankleitzahl 8", | ||||
| 	"Bankbezeichnung 8", | ||||
| 	"Bankkonto-Nummer 8", | ||||
| 	"Länderkennzeichen 8", | ||||
| 	"IBAN 8", | ||||
| 	"Leerfeld 9", | ||||
| 	"SWIFT-Code 8", | ||||
| 	"Abw. Kontoinhaber 8", | ||||
| 	"Kennz. Haupt-Bankverb. 8", | ||||
| 	"Bankverb 8 gültig von", | ||||
| 	"Bankverb 8 gültig bis", | ||||
| 	"Bankleitzahl 9", | ||||
| 	"Bankbezeichnung 9", | ||||
| 	"Bankkonto-Nummer 9", | ||||
| 	"Länderkennzeichen 9", | ||||
| 	"IBAN 9", | ||||
| 	"Leerfeld 10", | ||||
| 	"SWIFT-Code 9", | ||||
| 	"Abw. Kontoinhaber 9", | ||||
| 	"Kennz. Haupt-Bankverb. 9", | ||||
| 	"Bankverb 9 gültig von", | ||||
| 	"Bankverb 9 gültig bis", | ||||
| 	"Bankleitzahl 10", | ||||
| 	"Bankbezeichnung 10", | ||||
| 	"Bankkonto-Nummer 10", | ||||
| 	"Länderkennzeichen 10", | ||||
| 	"IBAN 10", | ||||
| 	"Leerfeld 11", | ||||
| 	"SWIFT-Code 10", | ||||
| 	"Abw. Kontoinhaber 10", | ||||
| 	"Kennz. Haupt-Bankverb. 10", | ||||
| 	"Bankverb 10 gültig von", | ||||
| 	"Bankverb 10 gültig bis", | ||||
| 	"Nummer Fremdsystem", | ||||
| 	"Insolvent", | ||||
| 	"SEPA-Mandatsreferenz 1", | ||||
| 	"SEPA-Mandatsreferenz 2", | ||||
| 	"SEPA-Mandatsreferenz 3", | ||||
| 	"SEPA-Mandatsreferenz 4", | ||||
| 	"SEPA-Mandatsreferenz 5", | ||||
| 	"SEPA-Mandatsreferenz 6", | ||||
| 	"SEPA-Mandatsreferenz 7", | ||||
| 	"SEPA-Mandatsreferenz 8", | ||||
| 	"SEPA-Mandatsreferenz 9", | ||||
| 	"SEPA-Mandatsreferenz 10", | ||||
| 	"Verknüpftes OPOS-Konto", | ||||
| 	"Mahnsperre bis", | ||||
| 	"Lastschriftsperre bis", | ||||
| 	"Zahlungssperre bis", | ||||
| 	"Gebührenberechnung", | ||||
| 	"Mahngebühr 1", | ||||
| 	"Mahngebühr 2", | ||||
| 	"Mahngebühr 3", | ||||
| 	"Pauschalberechnung", | ||||
| 	"Verzugspauschale 1", | ||||
| 	"Verzugspauschale 2", | ||||
| 	"Verzugspauschale 3", | ||||
| 	"Alternativer Suchname", | ||||
| 	"Status", | ||||
| 	"Anschrift manuell geändert (Korrespondenzadresse)", | ||||
| 	"Anschrift individuell (Korrespondenzadresse)", | ||||
| 	"Anschrift manuell geändert (Rechnungsadresse)", | ||||
| 	"Anschrift individuell (Rechnungsadresse)", | ||||
| 	"Fristberechnung bei Debitor", | ||||
| 	"Mahnfrist 1", | ||||
| 	"Mahnfrist 2", | ||||
| 	"Mahnfrist 3", | ||||
| 	"Letzte Frist" | ||||
| ] | ||||
| 
 | ||||
| ACCOUNT_NAME_COLUMNS = [ | ||||
| 	# Account number | ||||
| 	"Konto", | ||||
| 	# Account name | ||||
| 	"Kontenbeschriftung", | ||||
| 	# Language of the account name  | ||||
| 	# "de-DE" or "en-GB" | ||||
| 	"Sprach-ID" | ||||
| ] | ||||
| 
 | ||||
| QUERY_REPORT_COLUMNS = [ | ||||
| 	{ | ||||
| 		"label": "Umsatz (ohne Soll/Haben-Kz)", | ||||
| 		"fieldname": "Umsatz (ohne Soll/Haben-Kz)", | ||||
| 		"fieldtype": "Currency", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Soll/Haben-Kennzeichen", | ||||
| 		"fieldname": "Soll/Haben-Kennzeichen", | ||||
| 		"fieldtype": "Data", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Kontonummer", | ||||
| 		"fieldname": "Kontonummer", | ||||
| 		"fieldtype": "Data", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Gegenkonto (ohne BU-Schlüssel)", | ||||
| 		"fieldname": "Gegenkonto (ohne BU-Schlüssel)", | ||||
| 		"fieldtype": "Data", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Belegdatum", | ||||
| 		"fieldname": "Belegdatum", | ||||
| 		"fieldtype": "Date", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Buchungstext", | ||||
| 		"fieldname": "Buchungstext", | ||||
| 		"fieldtype": "Text", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Beleginfo - Art 1", | ||||
| 		"fieldname": "Beleginfo - Art 1", | ||||
| 		"fieldtype": "Data", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Beleginfo - Inhalt 1", | ||||
| 		"fieldname": "Beleginfo - Inhalt 1", | ||||
| 		"fieldtype": "Data", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Beleginfo - Art 2", | ||||
| 		"fieldname": "Beleginfo - Art 2", | ||||
| 		"fieldtype": "Data", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"label": "Beleginfo - Inhalt 2", | ||||
| 		"fieldname": "Beleginfo - Inhalt 2", | ||||
| 		"fieldtype": "Data", | ||||
| 	} | ||||
| ] | ||||
| 
 | ||||
| class DataCategory(): | ||||
| 	"""Field of the CSV Header.""" | ||||
| 
 | ||||
| 	DEBTORS_CREDITORS = "16" | ||||
| 	ACCOUNT_NAMES = "20" | ||||
| 	TRANSACTIONS = "21" | ||||
| 	POSTING_TEXT_CONSTANTS = "67" | ||||
| 
 | ||||
| class FormatName(): | ||||
| 	"""Field of the CSV Header, corresponds to DataCategory.""" | ||||
| 
 | ||||
| 	DEBTORS_CREDITORS = "Debitoren/Kreditoren" | ||||
| 	ACCOUNT_NAMES = "Kontenbeschriftungen" | ||||
| 	TRANSACTIONS = "Buchungsstapel" | ||||
| 	POSTING_TEXT_CONSTANTS = "Buchungstextkonstanten" | ||||
| 
 | ||||
| class Transactions(): | ||||
| 	DATA_CATEGORY = DataCategory.TRANSACTIONS | ||||
| 	FORMAT_NAME = FormatName.TRANSACTIONS | ||||
| 	COLUMNS = TRANSACTION_COLUMNS | ||||
| 
 | ||||
| class DebtorsCreditors(): | ||||
| 	DATA_CATEGORY = DataCategory.DEBTORS_CREDITORS | ||||
| 	FORMAT_NAME = FormatName.DEBTORS_CREDITORS | ||||
| 	COLUMNS = DEBTOR_CREDITOR_COLUMNS | ||||
| 
 | ||||
| class AccountNames(): | ||||
| 	DATA_CATEGORY = DataCategory.ACCOUNT_NAMES | ||||
| 	FORMAT_NAME = FormatName.ACCOUNT_NAMES | ||||
| 	COLUMNS = ACCOUNT_NAME_COLUMNS | ||||
							
								
								
									
										244
									
								
								erpnext/regional/report/datev/test_datev.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,244 @@ | ||||
| # coding=utf-8 | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import os | ||||
| import json | ||||
| import zipfile | ||||
| from six import BytesIO | ||||
| from unittest import TestCase | ||||
| 
 | ||||
| import frappe | ||||
| from frappe.utils import getdate, today, now_datetime, cstr | ||||
| from frappe.test_runner import make_test_objects | ||||
| from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice | ||||
| from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts | ||||
| 
 | ||||
| from erpnext.regional.report.datev.datev import validate | ||||
| from erpnext.regional.report.datev.datev import get_transactions | ||||
| from erpnext.regional.report.datev.datev import get_customers | ||||
| from erpnext.regional.report.datev.datev import get_suppliers | ||||
| from erpnext.regional.report.datev.datev import get_account_names | ||||
| from erpnext.regional.report.datev.datev import get_datev_csv | ||||
| from erpnext.regional.report.datev.datev import get_header | ||||
| from erpnext.regional.report.datev.datev import download_datev_csv | ||||
| 
 | ||||
| from erpnext.regional.report.datev.datev_constants import DataCategory | ||||
| from erpnext.regional.report.datev.datev_constants import Transactions | ||||
| from erpnext.regional.report.datev.datev_constants import DebtorsCreditors | ||||
| from erpnext.regional.report.datev.datev_constants import AccountNames | ||||
| from erpnext.regional.report.datev.datev_constants import QUERY_REPORT_COLUMNS | ||||
| 
 | ||||
| def make_company(company_name, abbr): | ||||
| 	if not frappe.db.exists("Company", company_name): | ||||
| 		company = frappe.get_doc({ | ||||
| 			"doctype": "Company", | ||||
| 			"company_name": company_name, | ||||
| 			"abbr": abbr, | ||||
| 			"default_currency": "EUR", | ||||
| 			"country": "Germany", | ||||
| 			"create_chart_of_accounts_based_on": "Standard Template", | ||||
| 			"chart_of_accounts": "SKR04 mit Kontonummern" | ||||
| 		}) | ||||
| 		company.insert() | ||||
| 	else: | ||||
| 		company = frappe.get_doc("Company", company_name) | ||||
| 
 | ||||
| 	# indempotent | ||||
| 	company.create_default_warehouses() | ||||
| 
 | ||||
| 	if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}): | ||||
| 		company.create_default_cost_center() | ||||
| 
 | ||||
| 	company.save() | ||||
| 	return company | ||||
| 
 | ||||
| def setup_fiscal_year(): | ||||
| 	fiscal_year = None | ||||
| 	year = cstr(now_datetime().year) | ||||
| 	if not frappe.db.get_value("Fiscal Year", {"year": year}, "name"): | ||||
| 		try: | ||||
| 			fiscal_year = frappe.get_doc({ | ||||
| 				"doctype": "Fiscal Year", | ||||
| 				"year": year, | ||||
| 				"year_start_date": "{0}-01-01".format(year), | ||||
| 				"year_end_date": "{0}-12-31".format(year) | ||||
| 			}) | ||||
| 			fiscal_year.insert() | ||||
| 		except frappe.NameError: | ||||
| 			pass | ||||
| 
 | ||||
| 	if fiscal_year: | ||||
| 		fiscal_year.set_as_default() | ||||
| 
 | ||||
| def make_customer_with_account(customer_name, company): | ||||
| 	acc_name = frappe.db.get_value("Account", { | ||||
| 			"account_name": customer_name, | ||||
| 			"company": company.name | ||||
| 		}, "name") | ||||
| 
 | ||||
| 	if not acc_name: | ||||
| 		acc = frappe.get_doc({ | ||||
| 			"doctype": "Account", | ||||
| 			"parent_account": "1 - Forderungen aus Lieferungen und Leistungen - _TG", | ||||
| 			"account_name": customer_name, | ||||
| 			"company": company.name, | ||||
| 			"account_type": "Receivable", | ||||
| 			"account_number": "10001" | ||||
| 		}) | ||||
| 		acc.insert() | ||||
| 		acc_name = acc.name | ||||
| 
 | ||||
| 	if not frappe.db.exists("Customer", customer_name): | ||||
| 		customer = frappe.get_doc({ | ||||
| 			"doctype": "Customer",		 | ||||
| 			"customer_name": customer_name, | ||||
| 			"customer_type": "Company", | ||||
| 			"accounts": [{ | ||||
| 				"company": company.name, | ||||
| 				"account": acc_name | ||||
| 			}] | ||||
| 		}) | ||||
| 		customer.insert() | ||||
| 	else: | ||||
| 		customer = frappe.get_doc("Customer", customer_name) | ||||
| 
 | ||||
| 	return customer | ||||
| 
 | ||||
| def make_item(item_code, company): | ||||
| 	warehouse_name = frappe.db.get_value("Warehouse", { | ||||
| 			"warehouse_name": "Stores", | ||||
| 			"company": company.name | ||||
| 		}, "name") | ||||
| 
 | ||||
| 	if not frappe.db.exists("Item", item_code): | ||||
| 		item = frappe.get_doc({ | ||||
| 			"doctype": "Item", | ||||
| 			"item_code": item_code, | ||||
| 			"item_name": item_code, | ||||
| 			"description": item_code, | ||||
| 			"item_group": "All Item Groups", | ||||
| 			"is_stock_item": 0, | ||||
| 			"is_purchase_item": 0, | ||||
| 			"is_customer_provided_item": 0, | ||||
| 			"item_defaults": [{ | ||||
| 				"default_warehouse": warehouse_name, | ||||
| 				"company": company.name | ||||
| 			}] | ||||
| 		}) | ||||
| 		item.insert() | ||||
| 	else: | ||||
| 		item = frappe.get_doc("Item", item_code) | ||||
| 	return item | ||||
| 
 | ||||
| def make_datev_settings(company): | ||||
| 	if not frappe.db.exists("DATEV Settings", company.name): | ||||
| 		frappe.get_doc({ | ||||
| 			"doctype": "DATEV Settings", | ||||
| 			"client": company.name, | ||||
| 			"client_number": "12345", | ||||
| 			"consultant_number": "67890" | ||||
| 		}).insert() | ||||
| 
 | ||||
| 
 | ||||
| class TestDatev(TestCase): | ||||
| 	def setUp(self): | ||||
| 		self.company = make_company("_Test GmbH", "_TG") | ||||
| 		self.customer = make_customer_with_account("_Test Kunde GmbH", self.company) | ||||
| 		self.filters = { | ||||
| 			"company": self.company.name, | ||||
| 			"from_date": today(), | ||||
| 			"to_date": today() | ||||
| 		} | ||||
| 
 | ||||
| 		make_datev_settings(self.company) | ||||
| 		item = make_item("_Test Item", self.company) | ||||
| 		setup_fiscal_year() | ||||
| 
 | ||||
| 		warehouse = frappe.db.get_value("Item Default", { | ||||
| 				"parent": item.name,  | ||||
| 				"company": self.company.name | ||||
| 			}, "default_warehouse") | ||||
| 
 | ||||
| 		income_account = frappe.db.get_value("Account", { | ||||
| 				"account_number": "4200",  | ||||
| 				"company": self.company.name | ||||
| 			}, "name") | ||||
| 
 | ||||
| 		tax_account = frappe.db.get_value("Account", { | ||||
| 				"account_number": "3806",  | ||||
| 				"company": self.company.name | ||||
| 			}, "name") | ||||
| 
 | ||||
| 		si = create_sales_invoice( | ||||
| 			company=self.company.name, | ||||
| 			customer=self.customer.name, | ||||
| 			currency=self.company.default_currency, | ||||
| 			debit_to=self.customer.accounts[0].account, | ||||
| 			income_account="4200 - Erlöse - _TG", | ||||
| 			expense_account="6990 - Herstellungskosten - _TG", | ||||
| 			cost_center=self.company.cost_center, | ||||
| 			warehouse=warehouse, | ||||
| 			item=item.name, | ||||
| 			do_not_save=1 | ||||
| 		) | ||||
| 
 | ||||
| 		si.append("taxes", { | ||||
| 			"charge_type": "On Net Total", | ||||
| 			"account_head": tax_account, | ||||
| 			"description": "Umsatzsteuer 19 %", | ||||
| 			"rate": 19 | ||||
| 		}) | ||||
| 
 | ||||
| 		si.save() | ||||
| 		si.submit() | ||||
| 
 | ||||
| 	def test_columns(self): | ||||
| 		def is_subset(get_data, allowed_keys): | ||||
| 			""" | ||||
| 			Validate that the dict contains only allowed keys. | ||||
| 			 | ||||
| 			Params: | ||||
| 			get_data -- Function that returns a list of dicts. | ||||
| 			allowed_keys -- List of allowed keys | ||||
| 			""" | ||||
| 			data = get_data(self.filters) | ||||
| 			if data == []: | ||||
| 				# No data and, therefore, no columns is okay | ||||
| 				return True | ||||
| 			actual_set = set(data[0].keys()) | ||||
| 			# allowed set must be interpreted as unicode to match the actual set | ||||
| 			allowed_set = set({frappe.as_unicode(key) for key in allowed_keys}) | ||||
| 			return actual_set.issubset(allowed_set) | ||||
| 
 | ||||
| 		self.assertTrue(is_subset(get_transactions, Transactions.COLUMNS)) | ||||
| 		self.assertTrue(is_subset(get_customers, DebtorsCreditors.COLUMNS)) | ||||
| 		self.assertTrue(is_subset(get_suppliers, DebtorsCreditors.COLUMNS)) | ||||
| 		self.assertTrue(is_subset(get_account_names, AccountNames.COLUMNS)) | ||||
| 
 | ||||
| 	def test_header(self): | ||||
| 		self.assertTrue(Transactions.DATA_CATEGORY in get_header(self.filters, Transactions)) | ||||
| 		self.assertTrue(AccountNames.DATA_CATEGORY in get_header(self.filters, AccountNames)) | ||||
| 		self.assertTrue(DebtorsCreditors.DATA_CATEGORY in get_header(self.filters, DebtorsCreditors)) | ||||
| 
 | ||||
| 	def test_csv(self): | ||||
| 		test_data = [{ | ||||
| 			"Umsatz (ohne Soll/Haben-Kz)": 100, | ||||
| 			"Soll/Haben-Kennzeichen": "H", | ||||
| 			"Kontonummer": "4200", | ||||
| 			"Gegenkonto (ohne BU-Schlüssel)": "10000", | ||||
| 			"Belegdatum": today(), | ||||
| 			"Buchungstext": "No remark", | ||||
| 			"Beleginfo - Art 1": "Sales Invoice", | ||||
| 			"Beleginfo - Inhalt 1": "SINV-0001" | ||||
| 		}] | ||||
| 		get_datev_csv(data=test_data, filters=self.filters, csv_class=Transactions) | ||||
| 
 | ||||
| 	def test_download(self): | ||||
| 		"""Assert that the returned file is a ZIP file.""" | ||||
| 		download_datev_csv(self.filters) | ||||
| 
 | ||||
| 		# zipfile.is_zipfile() expects a file-like object | ||||
| 		zip_buffer = BytesIO() | ||||
| 		zip_buffer.write(frappe.response['filecontent']) | ||||
| 
 | ||||
| 		self.assertTrue(zipfile.is_zipfile(zip_buffer)) | ||||
| @ -52,7 +52,7 @@ frappe.query_reports["GSTR-1"] = { | ||||
| 	], | ||||
| 	onload: function (report) { | ||||
| 
 | ||||
| 		report.page.add_inner_button(__("Download as Json"), function () { | ||||
| 		report.page.add_inner_button(__("Download as JSON"), function () { | ||||
| 			var filters = report.get_values(); | ||||
| 
 | ||||
| 			const args = { | ||||
|  | ||||
| @ -204,6 +204,40 @@ class Customer(TransactionBase): | ||||
| 		else: | ||||
| 			frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually.")) | ||||
| 
 | ||||
| 	def create_onboarding_docs(self, args): | ||||
| 		defaults = frappe.defaults.get_defaults() | ||||
| 		for i in range(1, args.get('max_count')): | ||||
| 			customer = args.get('customer_name_' + str(i)) | ||||
| 			if customer: | ||||
| 				try: | ||||
| 					doc = frappe.get_doc({ | ||||
| 						'doctype': self.doctype, | ||||
| 						'customer_name': customer, | ||||
| 						'customer_type': 'Company', | ||||
| 						'customer_group': _('Commercial'), | ||||
| 						'territory': defaults.get('country'), | ||||
| 						'company': defaults.get('company') | ||||
| 					}).insert() | ||||
| 
 | ||||
| 					if args.get('customer_email_' + str(i)): | ||||
| 						create_contact(customer, self.doctype, | ||||
| 							doc.name, args.get("customer_email_" + str(i))) | ||||
| 				except frappe.NameError: | ||||
| 					pass | ||||
| 
 | ||||
| def create_contact(contact, party_type, party, email): | ||||
| 	"""Create contact based on given contact name""" | ||||
| 	contact = contact.split(' ') | ||||
| 
 | ||||
| 	contact = frappe.get_doc({ | ||||
| 		'doctype': 'Contact', | ||||
| 		'first_name': contact[0], | ||||
| 		'last_name': len(contact) > 1 and contact[1] or "" | ||||
| 	}) | ||||
| 	contact.append('email_ids', dict(email_id=email, is_primary=1)) | ||||
| 	contact.append('links', dict(link_doctype=party_type, link_name=party)) | ||||
| 	contact.insert() | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_loyalty_programs(doc): | ||||
| 	''' returns applicable loyalty programs for a customer ''' | ||||
|  | ||||
| @ -202,7 +202,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( | ||||
| 					} | ||||
| 				} | ||||
| 				// payment request
 | ||||
| 				if(flt(doc.per_billed)==0) { | ||||
| 				if(flt(doc.per_billed)<100) { | ||||
| 					this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create')); | ||||
| 					this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create')); | ||||
| 				} | ||||
|  | ||||
| @ -834,6 +834,10 @@ def make_purchase_order(source_name, for_supplier=None, selected_items=[], targe | ||||
| 		for item in sales_order.items: | ||||
| 			if item.supplier and item.supplier not in suppliers: | ||||
| 				suppliers.append(item.supplier) | ||||
| 
 | ||||
| 	if not suppliers: | ||||
| 		frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order.")) | ||||
| 
 | ||||
| 	for supplier in suppliers: | ||||
| 		po =frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")}) | ||||
| 		if len(po) == 0: | ||||
|  | ||||
| @ -12,6 +12,7 @@ from erpnext.selling.doctype.sales_order.sales_order import make_work_orders | ||||
| from erpnext.controllers.accounts_controller import update_child_qty_rate | ||||
| import json | ||||
| from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request | ||||
| from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order | ||||
| 
 | ||||
| class TestSalesOrder(unittest.TestCase): | ||||
| 	def tearDown(self): | ||||
| @ -819,6 +820,25 @@ class TestSalesOrder(unittest.TestCase): | ||||
| 		mr_doc = frappe.get_doc('Material Request',mr.get('name')) | ||||
| 		self.assertEqual(mr_doc.items[0].sales_order, so.name) | ||||
| 
 | ||||
| 	def test_so_optional_blanket_order(self): | ||||
| 		""" | ||||
| 			Expected result: Blanket order Ordered Quantity should only be affected on Sales Order with against_blanket_order = 1. | ||||
| 			Second Sales Order should not add on to Blanket Orders Ordered Quantity. | ||||
| 		""" | ||||
| 
 | ||||
| 		bo = make_blanket_order(blanket_order_type = "Selling", quantity = 10, rate = 10) | ||||
| 
 | ||||
| 		so = make_sales_order(item_code= "_Test Item", qty = 5, against_blanket_order = 1) | ||||
| 		so_doc = frappe.get_doc('Sales Order', so.get('name')) | ||||
| 		# To test if the SO has a Blanket Order | ||||
| 		self.assertTrue(so_doc.items[0].blanket_order) | ||||
| 
 | ||||
| 		so = make_sales_order(item_code= "_Test Item", qty = 5, against_blanket_order = 0) | ||||
| 		so_doc = frappe.get_doc('Sales Order', so.get('name')) | ||||
| 		# To test if the SO does NOT have a Blanket Order | ||||
| 		self.assertEqual(so_doc.items[0].blanket_order, None) | ||||
| 
 | ||||
| 
 | ||||
| def make_sales_order(**args): | ||||
| 	so = frappe.new_doc("Sales Order") | ||||
| 	args = frappe._dict(args) | ||||
| @ -845,7 +865,8 @@ def make_sales_order(**args): | ||||
| 			"warehouse": args.warehouse, | ||||
| 			"qty": args.qty or 10, | ||||
| 			"uom": args.uom or None, | ||||
| 			"rate": args.rate or 100 | ||||
| 			"rate": args.rate or 100, | ||||
| 			"against_blanket_order": args.against_blanket_order | ||||
| 		}) | ||||
| 
 | ||||
| 	so.delivery_date = add_days(so.transaction_date, 10) | ||||
|  | ||||
| @ -68,6 +68,7 @@ | ||||
|   "target_warehouse", | ||||
|   "prevdoc_docname", | ||||
|   "col_break4", | ||||
|   "against_blanket_order", | ||||
|   "blanket_order", | ||||
|   "blanket_order_rate", | ||||
|   "planning_section", | ||||
| @ -574,6 +575,7 @@ | ||||
|    "report_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.against_blanket_order", | ||||
|    "fieldname": "blanket_order", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Blanket Order", | ||||
| @ -581,6 +583,7 @@ | ||||
|    "options": "Blanket Order" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.against_blanket_order", | ||||
|    "fieldname": "blanket_order_rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Blanket Order Rate", | ||||
| @ -741,11 +744,17 @@ | ||||
|    "fieldname": "image_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Image" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "against_blanket_order", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Against Blanket Order" | ||||
|   } | ||||
|  ], | ||||
|  "idx": 1, | ||||
|  "istable": 1, | ||||
|  "modified": "2019-10-10 08:46:26.244823", | ||||
|  "modified": "2019-11-19 14:19:29.491945", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Selling", | ||||
|  "name": "Sales Order Item", | ||||
|  | ||||
| @ -1,611 +1,159 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "beta": 0,  | ||||
|  "creation": "2013-06-25 10:25:16",  | ||||
|  "custom": 0,  | ||||
|  "description": "Settings for Selling Module",  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "Other",  | ||||
|  "editable_grid": 0,  | ||||
|  "engine": "InnoDB",  | ||||
|  "creation": "2013-06-25 10:25:16", | ||||
|  "description": "Settings for Selling Module", | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "Other", | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "cust_master_name", | ||||
|   "campaign_naming_by", | ||||
|   "customer_group", | ||||
|   "territory", | ||||
|   "selling_price_list", | ||||
|   "close_opportunity_after_days", | ||||
|   "default_valid_till", | ||||
|   "column_break_5", | ||||
|   "so_required", | ||||
|   "dn_required", | ||||
|   "sales_update_frequency", | ||||
|   "maintain_same_sales_rate", | ||||
|   "editable_price_list_rate", | ||||
|   "allow_multiple_items", | ||||
|   "allow_against_multiple_purchase_orders", | ||||
|   "validate_selling_price", | ||||
|   "hide_tax_id" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "Customer Name",  | ||||
|    "fieldname": "cust_master_name",  | ||||
|    "fieldtype": "Select",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Customer Naming By",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Customer Name\nNaming Series",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "Customer Name", | ||||
|    "fieldname": "cust_master_name", | ||||
|    "fieldtype": "Select", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Customer Naming By", | ||||
|    "options": "Customer Name\nNaming Series" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "campaign_naming_by",  | ||||
|    "fieldtype": "Select",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Campaign Naming By",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Campaign Name\nNaming Series",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "campaign_naming_by", | ||||
|    "fieldtype": "Select", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Campaign Naming By", | ||||
|    "options": "Campaign Name\nNaming Series" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "",  | ||||
|    "fieldname": "customer_group",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Default Customer Group",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Customer Group",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "customer_group", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Default Customer Group", | ||||
|    "options": "Customer Group" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "",  | ||||
|    "fieldname": "territory",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Default Territory",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Territory",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "territory", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Default Territory", | ||||
|    "options": "Territory" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "selling_price_list",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Default Price List",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Price List",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "selling_price_list", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Default Price List", | ||||
|    "options": "Price List" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "15",  | ||||
|    "description": "Auto close Opportunity after 15 days",  | ||||
|    "fieldname": "close_opportunity_after_days",  | ||||
|    "fieldtype": "Int",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Close Opportunity After Days",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "15", | ||||
|    "description": "Auto close Opportunity after 15 days", | ||||
|    "fieldname": "close_opportunity_after_days", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Close Opportunity After Days" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "default_valid_till",  | ||||
|    "fieldtype": "Data",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Default Quotation Validity Days",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "default_valid_till", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Default Quotation Validity Days" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "column_break_5",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "column_break_5", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "so_required",  | ||||
|    "fieldtype": "Select",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Sales Order Required",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "No\nYes",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "description": "Only for Stock Items", | ||||
|    "fieldname": "so_required", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Sales Order Required", | ||||
|    "options": "No\nYes" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "dn_required",  | ||||
|    "fieldtype": "Select",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Delivery Note Required",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "No\nYes",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "dn_required", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Delivery Note Required", | ||||
|    "options": "No\nYes" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "Each Transaction",  | ||||
|    "description": "How often should project and company be updated based on Sales Transactions.",  | ||||
|    "fieldname": "sales_update_frequency",  | ||||
|    "fieldtype": "Select",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Sales Update Frequency",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Each Transaction\nDaily\nMonthly",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "Each Transaction", | ||||
|    "description": "How often should project and company be updated based on Sales Transactions.", | ||||
|    "fieldname": "sales_update_frequency", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Sales Update Frequency", | ||||
|    "options": "Each Transaction\nDaily\nMonthly", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "maintain_same_sales_rate",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Maintain Same Rate Throughout Sales Cycle",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "fieldname": "maintain_same_sales_rate", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Maintain Same Rate Throughout Sales Cycle" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "editable_price_list_rate",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Allow user to edit Price List Rate in transactions",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "fieldname": "editable_price_list_rate", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow user to edit Price List Rate in transactions" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "allow_multiple_items",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Allow Item to be added multiple times in a transaction",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "fieldname": "allow_multiple_items", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow Item to be added multiple times in a transaction" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "allow_against_multiple_purchase_orders",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Allow multiple Sales Orders against a Customer's Purchase Order",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "fieldname": "allow_against_multiple_purchase_orders", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow multiple Sales Orders against a Customer's Purchase Order" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "validate_selling_price",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "fieldname": "validate_selling_price", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "hide_tax_id",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Hide Customer's Tax Id from Sales Transactions",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|    "default": "0", | ||||
|    "fieldname": "hide_tax_id", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Hide Customer's Tax Id from Sales Transactions" | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "icon": "fa fa-cog",  | ||||
|  "idx": 1,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 1,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2018-06-25 12:56:16.332039",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Selling",  | ||||
|  "name": "Selling Settings",  | ||||
|  "owner": "Administrator",  | ||||
|  ], | ||||
|  "icon": "fa fa-cog", | ||||
|  "idx": 1, | ||||
|  "issingle": 1, | ||||
|  "modified": "2019-11-25 18:35:51.472653", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Selling", | ||||
|  "name": "Selling Settings", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 0,  | ||||
|    "email": 1,  | ||||
|    "export": 0,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 0,  | ||||
|    "role": "System Manager",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "create": 1, | ||||
|    "email": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "role": "System Manager", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "quick_entry": 0,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_changes": 0,  | ||||
|  "track_seen": 0,  | ||||
|  "track_views": 0 | ||||
| } | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| # import frappe | ||||
| import unittest | ||||
| 
 | ||||
| class TestSellingSettings(unittest.TestCase): | ||||
| 	pass | ||||
| @ -7,7 +7,7 @@ from frappe.utils import flt | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| 	if not filters: filters = {} | ||||
| 	 | ||||
| 
 | ||||
| 	columns = get_columns() | ||||
| 	iwq_map = get_item_warehouse_quantity_map() | ||||
| 	item_map = get_item_details() | ||||
| @ -15,22 +15,23 @@ def execute(filters=None): | ||||
| 	for sbom, warehouse in iwq_map.items(): | ||||
| 		total = 0 | ||||
| 		total_qty = 0 | ||||
| 		 | ||||
| 
 | ||||
| 		for wh, item_qty in warehouse.items(): | ||||
| 			total += 1 | ||||
| 			row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description,  | ||||
| 					item_map.get(sbom).stock_uom, wh] | ||||
| 			available_qty = item_qty | ||||
| 			total_qty += flt(available_qty) | ||||
| 			row += [available_qty] | ||||
| 			 | ||||
| 			if available_qty: | ||||
| 				data.append(row) | ||||
| 				if (total == len(warehouse)): | ||||
| 					row = ["", "", "Total", "", "", total_qty] | ||||
| 			if item_map.get(sbom): | ||||
| 				row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description, | ||||
| 						item_map.get(sbom).stock_uom, wh] | ||||
| 				available_qty = item_qty | ||||
| 				total_qty += flt(available_qty) | ||||
| 				row += [available_qty] | ||||
| 
 | ||||
| 				if available_qty: | ||||
| 					data.append(row) | ||||
| 					if (total == len(warehouse)): | ||||
| 						row = ["", "", "Total", "", "", total_qty] | ||||
| 						data.append(row) | ||||
| 	return columns, data | ||||
| 		 | ||||
| 
 | ||||
| def get_columns(): | ||||
| 	columns = ["Item Code:Link/Item:100", "Item Name::100", "Description::120", \ | ||||
| 				"UOM:Link/UOM:80", "Warehouse:Link/Warehouse:100", "Quantity::100"] | ||||
|  | ||||
| @ -0,0 +1,49 @@ | ||||
| { | ||||
|  "add_more_button": 1, | ||||
|  "app": "ERPNext", | ||||
|  "creation": "2019-11-15 14:44:10.065014", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Setup Wizard Slide", | ||||
|  "domains": [], | ||||
|  "help_links": [ | ||||
|   { | ||||
|    "label": "Customers", | ||||
|    "video_id": "zsrrVDk6VBs" | ||||
|   } | ||||
|  ], | ||||
|  "idx": 0, | ||||
|  "image_src": "/assets/erpnext/images/illustrations/customer.png", | ||||
|  "max_count": 3, | ||||
|  "modified": "2019-11-26 18:26:15.888794", | ||||
|  "modified_by": "Administrator", | ||||
|  "name": "Add A Few Customers", | ||||
|  "owner": "Administrator", | ||||
|  "ref_doctype": "Customer", | ||||
|  "slide_desc": "", | ||||
|  "slide_fields": [ | ||||
|   { | ||||
|    "align": "", | ||||
|    "fieldname": "customer_name", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Customer Name", | ||||
|    "placeholder": "", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "align": "", | ||||
|    "fieldtype": "Column Break", | ||||
|    "reqd": 0 | ||||
|   }, | ||||
|   { | ||||
|    "align": "", | ||||
|    "fieldname": "customer_email", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Email ID", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "slide_order": 40, | ||||
|  "slide_title": "Add A Few Customers", | ||||
|  "slide_type": "Create", | ||||
|  "submit_method": "" | ||||
| } | ||||
| @ -1,8 +0,0 @@ | ||||
| // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('Setup Progress', { | ||||
| 	refresh: function() { | ||||
| 
 | ||||
| 	} | ||||
| }); | ||||
| @ -1,123 +0,0 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "beta": 0,  | ||||
|  "creation": "2017-08-27 21:01:42.032109",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "actions_sb",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Actions",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "actions",  | ||||
|    "fieldtype": "Table",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Actions",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Setup Progress Action",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 1,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 1,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2017-09-21 11:52:56.106659",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Setup",  | ||||
|  "name": "Setup Progress",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 0,  | ||||
|    "delete": 0,  | ||||
|    "email": 1,  | ||||
|    "export": 0,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 0,  | ||||
|    "role": "All",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 1,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
| } | ||||
| @ -1,63 +0,0 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe, json | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class SetupProgress(Document): | ||||
| 	pass | ||||
| 
 | ||||
| def get_setup_progress(): | ||||
| 	if not getattr(frappe.local, "setup_progress", None): | ||||
| 		frappe.local.setup_progress = frappe.get_doc("Setup Progress", "Setup Progress") | ||||
| 
 | ||||
| 	return frappe.local.setup_progress | ||||
| 
 | ||||
| def get_action_completed_state(action_name): | ||||
| 	for d in get_setup_progress().actions: | ||||
| 		if d.action_name == action_name: | ||||
| 			return d.is_completed | ||||
| 
 | ||||
| def update_action_completed_state(action_name): | ||||
| 	action_table_doc = [d for d in get_setup_progress().actions | ||||
| 		if d.action_name == action_name][0] | ||||
| 	update_action(action_table_doc) | ||||
| 
 | ||||
| def update_action(doc): | ||||
| 	doctype = doc.action_doctype | ||||
| 	docname = doc.action_document | ||||
| 	field = doc.action_field | ||||
| 
 | ||||
| 	if not doc.is_completed: | ||||
| 		if doc.min_doc_count: | ||||
| 			if frappe.db.count(doctype) >= doc.min_doc_count: | ||||
| 				doc.is_completed = 1 | ||||
| 				doc.save() | ||||
| 		if docname and field: | ||||
| 			d = frappe.get_doc(doctype, docname) | ||||
| 			if d.get(field): | ||||
| 				doc.is_completed = 1 | ||||
| 				doc.save() | ||||
| 
 | ||||
| def update_domain_actions(domain): | ||||
| 	for d in get_setup_progress().actions: | ||||
| 		domains = json.loads(d.domains) | ||||
| 		if domains == [] or domain in domains: | ||||
| 			update_action(d) | ||||
| 
 | ||||
| def get_domain_actions_state(domain): | ||||
| 	state = {} | ||||
| 	for d in get_setup_progress().actions: | ||||
| 		domains = json.loads(d.domains) | ||||
| 		if domains == [] or domain in domains: | ||||
| 			state[d.action_name] = d.is_completed | ||||
| 	return state | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def set_action_completed_state(action_name): | ||||
| 	action_table_doc = [d for d in get_setup_progress().actions | ||||
| 		if d.action_name == action_name][0] | ||||
| 	action_table_doc.is_completed = 1 | ||||
| 	action_table_doc.save() | ||||
| @ -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: Setup Progress", function (assert) { | ||||
| 	let done = assert.async(); | ||||
| 
 | ||||
| 	// number of asserts
 | ||||
| 	assert.expect(1); | ||||
| 
 | ||||
| 	frappe.run_serially([ | ||||
| 		// insert a new Setup Progress
 | ||||
| 		() => frappe.tests.make('Setup Progress', [ | ||||
| 			// values to be set
 | ||||
| 			{key: 'value'} | ||||
| 		]), | ||||
| 		() => { | ||||
| 			assert.equal(cur_frm.doc.key, 'value'); | ||||
| 		}, | ||||
| 		() => done() | ||||
| 	]); | ||||
| 
 | ||||
| }); | ||||
| @ -1,9 +0,0 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import unittest | ||||
| 
 | ||||
| class TestSetupProgress(unittest.TestCase): | ||||
| 	pass | ||||
| @ -1,253 +0,0 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "beta": 0,  | ||||
|  "creation": "2017-08-27 21:00:40.715360",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "action_name",  | ||||
|    "fieldtype": "Data",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Action Name",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "action_doctype",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Action Doctype",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "DocType",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "action_document",  | ||||
|    "fieldtype": "Dynamic Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Action Document",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "action_doctype",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "action_field",  | ||||
|    "fieldtype": "Data",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Action Field",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "min_doc_count",  | ||||
|    "fieldtype": "Int",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Min Doc Count",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "domains",  | ||||
|    "fieldtype": "Code",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Domains",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "is_completed",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Is Completed",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 1,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 1,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2017-09-01 14:34:59.685730",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Setup",  | ||||
|  "name": "Setup Progress Action",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 1,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
| } | ||||
| @ -1,9 +0,0 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class SetupProgressAction(Document): | ||||
| 	pass | ||||
| @ -1,71 +0,0 @@ | ||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # License: GNU General Public License v3. See license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe, time | ||||
| from frappe.utils.selenium_testdriver import TestDriver | ||||
| 
 | ||||
| def run_setup_wizard_test(): | ||||
| 	driver = TestDriver() | ||||
| 	frappe.db.set_default('in_selenium', '1') | ||||
| 	frappe.db.commit() | ||||
| 
 | ||||
| 	driver.login('#page-setup-wizard') | ||||
| 	print('Running Setup Wizard Test...') | ||||
| 
 | ||||
| 	# Language slide | ||||
| 	driver.wait_for_ajax(True) | ||||
| 	time.sleep(1) | ||||
| 
 | ||||
| 	driver.set_select("language", "English (United States)") | ||||
| 	driver.wait_for_ajax(True) | ||||
| 	time.sleep(1) | ||||
| 	driver.click(".next-btn") | ||||
| 
 | ||||
| 	# Region slide | ||||
| 	driver.wait_for_ajax(True) | ||||
| 	driver.set_select("country", "India") | ||||
| 	driver.wait_for_ajax(True) | ||||
| 	time.sleep(1) | ||||
| 	driver.click(".next-btn") | ||||
| 
 | ||||
| 	# Profile slide | ||||
| 	driver.set_field("full_name", "Great Tester") | ||||
| 	driver.set_field("email", "great@example.com") | ||||
| 	driver.set_field("password", "test") | ||||
| 	driver.wait_for_ajax(True) | ||||
| 	time.sleep(1) | ||||
| 	driver.click(".next-btn") | ||||
| 	time.sleep(1) | ||||
| 
 | ||||
| 	# domain slide | ||||
| 	driver.set_multicheck("domains", ["Manufacturing"]) | ||||
| 	time.sleep(1) | ||||
| 	driver.click(".next-btn") | ||||
| 
 | ||||
| 	# Org slide | ||||
| 	driver.set_field("company_name", "For Testing") | ||||
| 	time.sleep(1) | ||||
| 	driver.print_console() | ||||
| 	driver.click(".next-btn") | ||||
| 
 | ||||
| 	driver.set_field("company_tagline", "Just for GST") | ||||
| 	driver.set_field("bank_account", "HDFC") | ||||
| 	time.sleep(3) | ||||
| 	driver.click(".complete-btn") | ||||
| 
 | ||||
| 	# Wait for desktop | ||||
| 	driver.wait_for('#page-desktop', timeout=600) | ||||
| 
 | ||||
| 	driver.print_console() | ||||
| 	time.sleep(3) | ||||
| 
 | ||||
| 	frappe.db.set_default('in_selenium', None) | ||||
| 	frappe.db.set_value("Company", "For Testing", "write_off_account", "Write Off - FT") | ||||
| 	frappe.db.set_value("Company", "For Testing", "exchange_gain_loss_account", "Exchange Gain/Loss - FT") | ||||
| 	frappe.db.commit() | ||||
| 
 | ||||
| 	driver.close() | ||||
| 
 | ||||
| 	return True | ||||